diff --git a/dom/base/Console.cpp b/dom/base/Console.cpp index 267d2343e9..bdcd9414b6 100644 --- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -10,6 +10,7 @@ #include "mozilla/dom/BlobBinding.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/File.h" +#include "mozilla/dom/StructuredCloneHelper.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/Maybe.h" #include "nsCycleCollectionParticipant.h" @@ -24,6 +25,7 @@ #include "xpcprivate.h" #include "nsContentUtils.h" #include "nsDocShell.h" +#include "nsProxyRelease.h" #include "nsIConsoleAPIStorage.h" #include "nsIDOMWindowUtils.h" @@ -69,90 +71,11 @@ ConsoleStructuredCloneData * It's not the best, but at least we are able to show something. */ -// This method is called by the Structured Clone Algorithm when some data has -// to be read. -static JSObject* -ConsoleStructuredCloneCallbacksRead(JSContext* aCx, - JSStructuredCloneReader* /* unused */, - uint32_t aTag, uint32_t aIndex, - void* aClosure) -{ - AssertIsOnMainThread(); - ConsoleStructuredCloneData* data = - static_cast(aClosure); - MOZ_ASSERT(data); - - if (aTag == CONSOLE_TAG_BLOB) { - MOZ_ASSERT(data->mBlobs.Length() > aIndex); - - JS::Rooted val(aCx); - { - nsRefPtr blob = - Blob::Create(data->mParent, data->mBlobs.ElementAt(aIndex)); - if (!ToJSValue(aCx, blob, &val)) { - return nullptr; - } - } - - return &val.toObject(); - } - - MOZ_CRASH("No other tags are supported."); - return nullptr; -} - -// This method is called by the Structured Clone Algorithm when some data has -// to be written. -static bool -ConsoleStructuredCloneCallbacksWrite(JSContext* aCx, - JSStructuredCloneWriter* aWriter, - JS::Handle aObj, - void* aClosure) -{ - ConsoleStructuredCloneData* data = - static_cast(aClosure); - MOZ_ASSERT(data); - - nsRefPtr blob; - if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) && - blob->Impl()->MayBeClonedToOtherThreads()) { - if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB, data->mBlobs.Length())) { - return false; - } - - data->mBlobs.AppendElement(blob->Impl()); - return true; - } - - JS::Rooted value(aCx, JS::ObjectOrNullValue(aObj)); - JS::Rooted jsString(aCx, JS::ToString(aCx, value)); - if (!jsString) { - return false; - } - - if (!JS_WriteString(aWriter, jsString)) { - return false; - } - - return true; -} - -static void -ConsoleStructuredCloneCallbacksError(JSContext* /* aCx */, - uint32_t /* aErrorId */) -{ - NS_WARNING("Failed to clone data for the Console API in workers."); -} - -static const JSStructuredCloneCallbacks gConsoleCallbacks = { - ConsoleStructuredCloneCallbacksRead, - ConsoleStructuredCloneCallbacksWrite, - ConsoleStructuredCloneCallbacksError -}; - class ConsoleCallData final { public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ConsoleCallData) + ConsoleCallData() : mMethodName(Console::MethodLog) , mPrivate(false) @@ -241,6 +164,10 @@ public: Maybe mTopStackFrame; Maybe> mReifiedStack; nsCOMPtr mStack; + +private: + ~ConsoleCallData() + { } }; // This class is used to clear any exception at the end of this method. @@ -262,6 +189,8 @@ private: }; class ConsoleRunnable : public nsRunnable + , public WorkerFeature + , public StructuredCloneHelperInternal { public: explicit ConsoleRunnable(Console* aConsole) @@ -274,6 +203,8 @@ public: virtual ~ConsoleRunnable() { + // Shutdown the StructuredCloneHelperInternal class. + Shutdown(); } bool @@ -287,16 +218,19 @@ public: return false; } - AutoSyncLoopHolder syncLoop(mWorkerPrivate); - mSyncLoopTarget = syncLoop.EventTarget(); - - if (NS_FAILED(NS_DispatchToMainThread(this))) { - JS_ReportError(cx, - "Failed to dispatch to main thread for the Console API!"); + if (NS_WARN_IF(!mWorkerPrivate->AddFeature(cx, this))) { return false; } - return syncLoop.Run(); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this))); + return true; + } + + virtual bool Notify(JSContext* aCx, workers::Status aStatus) override + { + // We don't care about the notification. We just want to keep the + // mWorkerPrivate alive. + return true; } private: @@ -317,17 +251,47 @@ private: RunWithWindow(window); } - nsRefPtr response = - new MainThreadStopSyncLoopRunnable(mWorkerPrivate, - mSyncLoopTarget.forget(), - true); - if (!response->Dispatch(nullptr)) { - NS_WARNING("Failed to dispatch response!"); - } - + PostDispatch(); return NS_OK; } + void + PostDispatch() + { + class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable + { + nsRefPtr mRunnable; + + public: + ConsoleReleaseRunnable(WorkerPrivate* aWorkerPrivate, + ConsoleRunnable* aRunnable) + : MainThreadWorkerControlRunnable(aWorkerPrivate) + , mRunnable(aRunnable) + { + MOZ_ASSERT(aRunnable); + } + + virtual bool + WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override + { + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + + aWorkerPrivate->RemoveFeature(aCx, mRunnable); + mRunnable->mConsole = nullptr; + return true; + } + + private: + ~ConsoleReleaseRunnable() + {} + }; + + nsRefPtr runnable = + new ConsoleReleaseRunnable(mWorkerPrivate, this); + runnable->Dispatch(nullptr); + } + void RunWithWindow(nsPIDOMWindow* aWindow) { @@ -386,14 +350,67 @@ protected: RunConsole(JSContext* aCx, nsPIDOMWindow* aOuterWindow, nsPIDOMWindow* aInnerWindow) = 0; + virtual JSObject* ReadCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + uint32_t aIndex) override + { + AssertIsOnMainThread(); + + if (aTag == CONSOLE_TAG_BLOB) { + MOZ_ASSERT(mClonedData.mBlobs.Length() > aIndex); + + JS::Rooted val(aCx); + { + nsRefPtr blob = + Blob::Create(mClonedData.mParent, mClonedData.mBlobs.ElementAt(aIndex)); + if (!ToJSValue(aCx, blob, &val)) { + return nullptr; + } + } + + return &val.toObject(); + } + + MOZ_CRASH("No other tags are supported."); + return nullptr; + } + + virtual bool WriteCallback(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle aObj) override + { + nsRefPtr blob; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) && + blob->Impl()->MayBeClonedToOtherThreads()) { + if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB, + mClonedData.mBlobs.Length())) { + return false; + } + + mClonedData.mBlobs.AppendElement(blob->Impl()); + return true; + } + + JS::Rooted value(aCx, JS::ObjectOrNullValue(aObj)); + JS::Rooted jsString(aCx, JS::ToString(aCx, value)); + if (!jsString) { + return false; + } + + if (!JS_WriteString(aWriter, jsString)) { + return false; + } + + return true; + } + WorkerPrivate* mWorkerPrivate; - // Raw pointer because this method is async and this object is kept alive by - // the caller. - Console* mConsole; + // This must be released on the worker thread. + nsRefPtr mConsole; -private: - nsCOMPtr mSyncLoopTarget; + ConsoleStructuredCloneData mClonedData; }; // This runnable appends a CallData object into the Console queue running on @@ -409,7 +426,30 @@ public: private: ~ConsoleCallDataRunnable() - { } + { + class ReleaseCallData final : public nsRunnable + { + public: + explicit ReleaseCallData(nsRefPtr& aCallData) + { + mCallData.swap(aCallData); + } + + NS_IMETHOD Run() override + { + mCallData = nullptr; + return NS_OK; + } + + private: + nsRefPtr mCallData; + }; + + nsRefPtr runnable = new ReleaseCallData(mCallData); + if(NS_FAILED(NS_DispatchToMainThread(runnable))) { + NS_WARNING("Failed to dispatch a ReleaseCallData runnable. Leaking."); + } + } bool PreDispatch(JSContext* aCx) override @@ -435,7 +475,7 @@ private: JS::Rooted value(aCx, JS::ObjectValue(*arguments)); - if (!mArguments.write(aCx, value, &gConsoleCallbacks, &mData)) { + if (!Write(aCx, value)) { return false; } @@ -473,12 +513,12 @@ private: } // Now we could have the correct window (if we are not window-less). - mData.mParent = aInnerWindow; + mClonedData.mParent = aInnerWindow; ProcessCallData(aCx); mCallData->CleanupJSObjects(); - mData.mParent = nullptr; + mClonedData.mParent = nullptr; } private: @@ -488,7 +528,7 @@ private: ClearException ce(aCx); JS::Rooted argumentsValue(aCx); - if (!mArguments.read(aCx, &argumentsValue, &gConsoleCallbacks, &mData)) { + if (!Read(aCx, &argumentsValue)) { return; } @@ -517,10 +557,7 @@ private: mConsole->ProcessCallData(mCallData); } - ConsoleCallData* mCallData; - - JSAutoStructuredCloneBuffer mArguments; - ConsoleStructuredCloneData mData; + nsRefPtr mCallData; }; // This runnable calls ProfileMethod() on the console on the main-thread. @@ -565,10 +602,11 @@ private: JS::Rooted value(aCx, JS::ObjectValue(*arguments)); - if (!mBuffer.write(aCx, value, &gConsoleCallbacks, &mData)) { + if (!Write(aCx, value)) { return false; } + mArguments.Clear(); return true; } @@ -579,11 +617,11 @@ private: ClearException ce(aCx); // Now we could have the correct window (if we are not window-less). - mData.mParent = aInnerWindow; + mClonedData.mParent = aInnerWindow; JS::Rooted argumentsValue(aCx); - bool ok = mBuffer.read(aCx, &argumentsValue, &gConsoleCallbacks, &mData); - mData.mParent = nullptr; + bool ok = Read(aCx, &argumentsValue); + mClonedData.mParent = nullptr; if (!ok) { return; @@ -617,9 +655,6 @@ private: nsString mAction; Sequence mArguments; - - JSAutoStructuredCloneBuffer mBuffer; - ConsoleStructuredCloneData mData; }; NS_IMPL_CYCLE_COLLECTION_CLASS(Console) @@ -1000,7 +1035,7 @@ Console::Method(JSContext* aCx, MethodName aMethodName, const nsAString& aMethodString, const Sequence& aData) { - nsAutoPtr callData(new ConsoleCallData()); + nsRefPtr callData(new ConsoleCallData()); ClearException ce(aCx); @@ -1137,8 +1172,6 @@ Console::Method(JSContext* aCx, MethodName aMethodName, return; } - // Note: we can pass the reference of callData because this runnable calls - // ProcessCallData() synchronously. nsRefPtr runnable = new ConsoleCallDataRunnable(this, callData); runnable->Dispatch(); diff --git a/dom/base/FileList.cpp b/dom/base/FileList.cpp index 8a90c026ea..642b167f98 100644 --- a/dom/base/FileList.cpp +++ b/dom/base/FileList.cpp @@ -21,30 +21,6 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(FileList) NS_IMPL_CYCLE_COLLECTING_RELEASE(FileList) -/* static */ already_AddRefed -FileList::Create(nsISupports* aParent, FileListClonedData* aData) -{ - MOZ_ASSERT(aData); - - nsRefPtr fileList = new FileList(aParent); - - const nsTArray>& blobImpls = aData->BlobImpls(); - for (uint32_t i = 0; i < blobImpls.Length(); ++i) { - const nsRefPtr& blobImpl = blobImpls[i]; - MOZ_ASSERT(blobImpl); - MOZ_ASSERT(blobImpl->IsFile()); - - nsRefPtr file = File::Create(aParent, blobImpl); - MOZ_ASSERT(file); - - if (NS_WARN_IF(!fileList->Append(file))) { - return nullptr; - } - } - - return fileList.forget(); -} - JSObject* FileList::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { @@ -67,19 +43,5 @@ FileList::Item(uint32_t aIndex, nsISupports** aFile) return NS_OK; } -already_AddRefed -FileList::CreateClonedData() const -{ - nsTArray> blobImpls; - for (uint32_t i = 0; i < mFiles.Length(); ++i) { - blobImpls.AppendElement(mFiles[i]->Impl()); - } - - nsRefPtr data = new FileListClonedData(blobImpls); - return data.forget(); -} - -NS_IMPL_ISUPPORTS0(FileListClonedData) - } // namespace dom } // namespace mozilla diff --git a/dom/base/FileList.h b/dom/base/FileList.h index 9193c64d27..772c6d7966 100644 --- a/dom/base/FileList.h +++ b/dom/base/FileList.h @@ -7,6 +7,8 @@ #ifndef mozilla_dom_FileList_h #define mozilla_dom_FileList_h +#include "mozilla/dom/BindingDeclarations.h" +#include "nsCycleCollectionParticipant.h" #include "nsIDOMFileList.h" #include "nsWrapperCache.h" @@ -16,27 +18,6 @@ namespace dom { class BlobImpls; class File; -class FileListClonedData final : public nsISupports -{ -public: - NS_DECL_THREADSAFE_ISUPPORTS - - explicit FileListClonedData(const nsTArray>& aBlobImpls) - : mBlobImpls(aBlobImpls) - {} - - const nsTArray>& BlobImpls() const - { - return mBlobImpls; - } - -private: - ~FileListClonedData() - {} - - const nsTArray> mBlobImpls; -}; - class FileList final : public nsIDOMFileList, public nsWrapperCache { @@ -50,9 +31,6 @@ public: : mParent(aParent) {} - static already_AddRefed - Create(nsISupports* aParent, FileListClonedData* aData); - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; @@ -114,9 +92,6 @@ public: return mFiles.Length(); } - // Useful for cloning - already_AddRefed CreateClonedData() const; - private: ~FileList() {} diff --git a/dom/base/PostMessageEvent.cpp b/dom/base/PostMessageEvent.cpp index 18e15bc4c4..f7dc034c8a 100644 --- a/dom/base/PostMessageEvent.cpp +++ b/dom/base/PostMessageEvent.cpp @@ -22,252 +22,13 @@ namespace mozilla { namespace dom { -namespace { - -struct StructuredCloneInfo -{ - PostMessageEvent* event; - nsPIDOMWindow* window; - - // This hashtable contains the transferred ports - used to avoid duplicates. - nsTArray> transferredPorts; - - // This array is populated when the ports are cloned. - nsTArray> clonedPorts; -}; - -} // anonymous namespace - -const JSStructuredCloneCallbacks PostMessageEvent::sPostMessageCallbacks = { - PostMessageEvent::ReadStructuredClone, - PostMessageEvent::WriteStructuredClone, - nullptr, - PostMessageEvent::ReadTransferStructuredClone, - PostMessageEvent::TransferStructuredClone, - PostMessageEvent::FreeTransferStructuredClone -}; - -/* static */ JSObject* -PostMessageEvent::ReadStructuredClone(JSContext* cx, - JSStructuredCloneReader* reader, - uint32_t tag, - uint32_t data, - void* closure) -{ - StructuredCloneInfo* scInfo = static_cast(closure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - if (tag == SCTAG_DOM_BLOB) { - NS_ASSERTION(!data, "Data should be empty"); - - // What we get back from the reader is a BlobImpl. - // From that we create a new File. - BlobImpl* blobImpl; - if (JS_ReadBytes(reader, &blobImpl, sizeof(blobImpl))) { - MOZ_ASSERT(blobImpl); - - // nsRefPtr needs to go out of scope before toObjectOrNull() is - // called because the static analysis thinks dereferencing XPCOM objects - // can GC (because in some cases it can!), and a return statement with a - // JSObject* type means that JSObject* is on the stack as a raw pointer - // while destructors are running. - JS::Rooted val(cx); - { - nsRefPtr blob = Blob::Create(scInfo->window, blobImpl); - if (!ToJSValue(cx, blob, &val)) { - return nullptr; - } - } - - return &val.toObject(); - } - } - - if (tag == SCTAG_DOM_FILELIST) { - NS_ASSERTION(!data, "Data should be empty"); - - // What we get back from the reader is a FileListClonedData. - // From that we create a new FileList. - FileListClonedData* fileListClonedData; - if (JS_ReadBytes(reader, &fileListClonedData, sizeof(fileListClonedData))) { - MOZ_ASSERT(fileListClonedData); - - // nsRefPtr needs to go out of scope before toObjectOrNull() is - // called because the static analysis thinks dereferencing XPCOM objects - // can GC (because in some cases it can!), and a return statement with a - // JSObject* type means that JSObject* is on the stack as a raw pointer - // while destructors are running. - JS::Rooted val(cx); - { - nsRefPtr fileList = - FileList::Create(scInfo->window, fileListClonedData); - if (!fileList || !ToJSValue(cx, fileList, &val)) { - return nullptr; - } - } - - return &val.toObject(); - } - } - - const JSStructuredCloneCallbacks* runtimeCallbacks = - js::GetContextStructuredCloneCallbacks(cx); - - if (runtimeCallbacks) { - return runtimeCallbacks->read(cx, reader, tag, data, nullptr); - } - - return nullptr; -} - -/* static */ bool -PostMessageEvent::WriteStructuredClone(JSContext* cx, - JSStructuredCloneWriter* writer, - JS::Handle obj, - void *closure) -{ - StructuredCloneInfo* scInfo = static_cast(closure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - // See if this is a File/Blob object. - { - Blob* blob = nullptr; - if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, obj, blob))) { - BlobImpl* blobImpl = blob->Impl(); - if (JS_WriteUint32Pair(writer, SCTAG_DOM_BLOB, 0) && - JS_WriteBytes(writer, &blobImpl, sizeof(blobImpl))) { - scInfo->event->StoreISupports(blobImpl); - return true; - } - } - } - - // See if this is a FileList object. - { - FileList* fileList = nullptr; - if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, obj, fileList))) { - nsRefPtr fileListClonedData = - fileList->CreateClonedData(); - MOZ_ASSERT(fileListClonedData); - FileListClonedData* ptr = fileListClonedData.get(); - if (JS_WriteUint32Pair(writer, SCTAG_DOM_FILELIST, 0) && - JS_WriteBytes(writer, &ptr, sizeof(ptr))) { - scInfo->event->StoreISupports(fileListClonedData); - return true; - } - } - } - - const JSStructuredCloneCallbacks* runtimeCallbacks = - js::GetContextStructuredCloneCallbacks(cx); - - if (runtimeCallbacks) { - return runtimeCallbacks->write(cx, writer, obj, nullptr); - } - - return false; -} - -/* static */ bool -PostMessageEvent::ReadTransferStructuredClone(JSContext* aCx, - JSStructuredCloneReader* reader, - uint32_t tag, void* aData, - uint64_t aExtraData, - void* aClosure, - JS::MutableHandle returnObject) -{ - StructuredCloneInfo* scInfo = static_cast(aClosure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - if (tag == SCTAG_DOM_MAP_MESSAGEPORT) { - MOZ_ASSERT(!aData); - // aExtraData is the index of this port identifier. - ErrorResult rv; - nsRefPtr port = - MessagePort::Create(scInfo->window, - scInfo->event->GetPortIdentifier(aExtraData), - rv); - if (NS_WARN_IF(rv.Failed())) { - return false; - } - - scInfo->clonedPorts.AppendElement(port); - - JS::Rooted value(aCx); - if (!GetOrCreateDOMReflector(aCx, port, &value)) { - JS_ClearPendingException(aCx); - return false; - } - - returnObject.set(&value.toObject()); - return true; - } - - return false; -} - -/* static */ bool -PostMessageEvent::TransferStructuredClone(JSContext* aCx, - JS::Handle aObj, - void* aClosure, - uint32_t* aTag, - JS::TransferableOwnership* aOwnership, - void** aContent, - uint64_t* aExtraData) -{ - StructuredCloneInfo* scInfo = static_cast(aClosure); - NS_ASSERTION(scInfo, "Must have scInfo!"); - - MessagePortBase* port = nullptr; - nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); - if (NS_SUCCEEDED(rv)) { - if (scInfo->transferredPorts.Contains(port)) { - // No duplicates. - return false; - } - - // We use aExtraData to store the index of this new port identifier. - MessagePortIdentifier* identifier = - scInfo->event->NewPortIdentifier(aExtraData); - - if (!port->CloneAndDisentangle(*identifier)) { - return false; - } - - scInfo->transferredPorts.AppendElement(port); - - *aTag = SCTAG_DOM_MAP_MESSAGEPORT; - *aOwnership = JS::SCTAG_TMO_CUSTOM; - *aContent = nullptr; - - return true; - } - - return false; -} - -/* static */ void -PostMessageEvent::FreeTransferStructuredClone(uint32_t aTag, - JS::TransferableOwnership aOwnership, - void *aContent, - uint64_t aExtraData, - void* aClosure) -{ - if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { - MOZ_ASSERT(aClosure); - MOZ_ASSERT(!aContent); - - StructuredCloneInfo* scInfo = static_cast(aClosure); - MessagePort::ForceClose(scInfo->event->GetPortIdentifier(aExtraData)); - } -} - PostMessageEvent::PostMessageEvent(nsGlobalWindow* aSource, const nsAString& aCallerOrigin, nsGlobalWindow* aTargetWindow, nsIPrincipal* aProvidedPrincipal, bool aTrustedCaller) -: mSource(aSource), +: StructuredCloneHelper(CloningSupported, TransferringSupported), + mSource(aSource), mCallerOrigin(aCallerOrigin), mTargetWindow(aTargetWindow), mProvidedPrincipal(aProvidedPrincipal), @@ -281,20 +42,6 @@ PostMessageEvent::~PostMessageEvent() MOZ_COUNT_DTOR(PostMessageEvent); } -const MessagePortIdentifier& -PostMessageEvent::GetPortIdentifier(uint64_t aId) -{ - MOZ_ASSERT(aId < mPortIdentifiers.Length()); - return mPortIdentifiers[aId]; -} - -MessagePortIdentifier* -PostMessageEvent::NewPortIdentifier(uint64_t* aPosition) -{ - *aPosition = mPortIdentifiers.Length(); - return mPortIdentifiers.AppendElement(); -} - NS_IMETHODIMP PostMessageEvent::Run() { @@ -346,14 +93,13 @@ PostMessageEvent::Run() } } - // Deserialize the structured clone data + ErrorResult rv; JS::Rooted messageData(cx); - StructuredCloneInfo scInfo; - scInfo.event = this; - scInfo.window = targetWindow; + nsCOMPtr window = targetWindow.get(); - if (!mBuffer.read(cx, &messageData, &sPostMessageCallbacks, &scInfo)) { - return NS_ERROR_DOM_DATA_CLONE_ERR; + Read(window, cx, &messageData, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); } // Create the event @@ -367,7 +113,7 @@ PostMessageEvent::Run() EmptyString(), mSource); event->SetPorts(new MessagePortList(static_cast(event.get()), - scInfo.clonedPorts)); + GetTransferredPorts())); // We can't simply call dispatchEvent on the window because doing so ends // up flipping the trusted bit on the event, and we don't want that to @@ -391,19 +137,5 @@ PostMessageEvent::Run() return NS_OK; } -bool -PostMessageEvent::Write(JSContext* aCx, JS::Handle aMessage, - JS::Handle aTransfer, nsPIDOMWindow* aWindow) -{ - // We *must* clone the data here, or the JS::Value could be modified - // by script - StructuredCloneInfo scInfo; - scInfo.event = this; - scInfo.window = aWindow; - - return mBuffer.write(aCx, aMessage, aTransfer, &sPostMessageCallbacks, - &scInfo); -} - -} // dom namespace -} // mozilla namespace +} // namespace dom +} // namespace mozilla diff --git a/dom/base/PostMessageEvent.h b/dom/base/PostMessageEvent.h index 0cd24f9657..e1e71becf9 100644 --- a/dom/base/PostMessageEvent.h +++ b/dom/base/PostMessageEvent.h @@ -7,7 +7,7 @@ #ifndef mozilla_dom_PostMessageEvent_h #define mozilla_dom_PostMessageEvent_h -#include "js/StructuredClone.h" +#include "mozilla/dom/StructuredCloneHelper.h" #include "nsCOMPtr.h" #include "mozilla/nsRefPtr.h" #include "nsTArray.h" @@ -26,6 +26,7 @@ class MessagePortIdentifier; * which asynchronously creates and dispatches events. */ class PostMessageEvent final : public nsRunnable + , public StructuredCloneHelper { public: NS_DECL_NSIRUNNABLE @@ -36,72 +37,17 @@ public: nsIPrincipal* aProvidedPrincipal, bool aTrustedCaller); - bool Write(JSContext* aCx, JS::Handle aMessage, - JS::Handle aTransfer, nsPIDOMWindow* aWindow); - private: ~PostMessageEvent(); - const MessagePortIdentifier& GetPortIdentifier(uint64_t aId); - - MessagePortIdentifier* NewPortIdentifier(uint64_t* aPosition); - - bool StoreISupports(nsISupports* aSupports) - { - mSupportsArray.AppendElement(aSupports); - return true; - } - - static JSObject* - ReadStructuredClone(JSContext* cx, - JSStructuredCloneReader* reader, - uint32_t tag, - uint32_t data, - void* closure); - - static bool - WriteStructuredClone(JSContext* cx, - JSStructuredCloneWriter* writer, - JS::Handle obj, - void *closure); - - static bool - ReadTransferStructuredClone(JSContext* aCx, - JSStructuredCloneReader* reader, - uint32_t tag, void* aData, - uint64_t aExtraData, - void* aClosure, - JS::MutableHandle returnObject); - - static bool - TransferStructuredClone(JSContext* aCx, - JS::Handle aObj, - void* aClosure, - uint32_t* aTag, - JS::TransferableOwnership* aOwnership, - void** aContent, - uint64_t* aExtraData); - - static void - FreeTransferStructuredClone(uint32_t aTag, - JS::TransferableOwnership aOwnership, - void *aContent, - uint64_t aExtraData, - void* aClosure); - - static const JSStructuredCloneCallbacks sPostMessageCallbacks; - - JSAutoStructuredCloneBuffer mBuffer; nsRefPtr mSource; nsString mCallerOrigin; nsRefPtr mTargetWindow; nsCOMPtr mProvidedPrincipal; bool mTrustedCaller; - nsTArray> mSupportsArray; - nsTArray mPortIdentifiers; }; -} // dom namespace -} // mozilla namespace +} // namespace dom +} // namespace mozilla #endif // mozilla_dom_PostMessageEvent_h diff --git a/dom/base/StructuredCloneHelper.cpp b/dom/base/StructuredCloneHelper.cpp new file mode 100644 index 0000000000..ed307e46ab --- /dev/null +++ b/dom/base/StructuredCloneHelper.cpp @@ -0,0 +1,516 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "StructuredCloneHelper.h" + +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/FileListBinding.h" +#include "mozilla/dom/StructuredCloneTags.h" + +namespace mozilla { +namespace dom { + +namespace { + +JSObject* +StructuredCloneCallbacksRead(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, uint32_t aIndex, + void* aClosure) +{ + StructuredCloneHelperInternal* helper = + static_cast(aClosure); + MOZ_ASSERT(helper); + return helper->ReadCallback(aCx, aReader, aTag, aIndex); +} + +bool +StructuredCloneCallbacksWrite(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle aObj, + void* aClosure) +{ + StructuredCloneHelperInternal* helper = + static_cast(aClosure); + MOZ_ASSERT(helper); + return helper->WriteCallback(aCx, aWriter, aObj); +} + +bool +StructuredCloneCallbacksReadTransfer(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + void* aClosure, + JS::MutableHandleObject aReturnObject) +{ + StructuredCloneHelperInternal* helper = + static_cast(aClosure); + MOZ_ASSERT(helper); + return helper->ReadTransferCallback(aCx, aReader, aTag, aContent, + aExtraData, aReturnObject); +} + +bool +StructuredCloneCallbacksWriteTransfer(JSContext* aCx, + JS::Handle aObj, + void* aClosure, + // Output: + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + StructuredCloneHelperInternal* helper = + static_cast(aClosure); + MOZ_ASSERT(helper); + return helper->WriteTransferCallback(aCx, aObj, aTag, aOwnership, aContent, + aExtraData); +} + +void +StructuredCloneCallbacksFreeTransfer(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData, + void* aClosure) +{ + StructuredCloneHelperInternal* helper = + static_cast(aClosure); + MOZ_ASSERT(helper); + return helper->FreeTransferCallback(aTag, aOwnership, aContent, aExtraData); +} + +void +StructuredCloneCallbacksError(JSContext* aCx, + uint32_t aErrorId) +{ + NS_WARNING("Failed to clone data."); +} + +const JSStructuredCloneCallbacks gCallbacks = { + StructuredCloneCallbacksRead, + StructuredCloneCallbacksWrite, + StructuredCloneCallbacksError, + StructuredCloneCallbacksReadTransfer, + StructuredCloneCallbacksWriteTransfer, + StructuredCloneCallbacksFreeTransfer +}; + +} // anonymous namespace + +// StructuredCloneHelperInternal class + +StructuredCloneHelperInternal::StructuredCloneHelperInternal() +#ifdef DEBUG + : mShutdownCalled(false) +#endif +{} + +StructuredCloneHelperInternal::~StructuredCloneHelperInternal() +{ +#ifdef DEBUG + MOZ_ASSERT(mShutdownCalled); +#endif +} + +void +StructuredCloneHelperInternal::Shutdown() +{ +#ifdef DEBUG + MOZ_ASSERT(!mShutdownCalled, "Shutdown already called!"); + mShutdownCalled = true; +#endif + + mBuffer = nullptr; +} + +bool +StructuredCloneHelperInternal::Write(JSContext* aCx, + JS::Handle aValue) +{ + MOZ_ASSERT(!mBuffer, "Double Write is not allowed"); + MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown."); + + mBuffer = new JSAutoStructuredCloneBuffer(&gCallbacks, this); + return mBuffer->write(aCx, aValue, &gCallbacks, this); +} + +bool +StructuredCloneHelperInternal::Write(JSContext* aCx, + JS::Handle aValue, + JS::Handle aTransfer) +{ + MOZ_ASSERT(!mBuffer, "Double Write is not allowed"); + MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown."); + + mBuffer = new JSAutoStructuredCloneBuffer(&gCallbacks, this); + return mBuffer->write(aCx, aValue, aTransfer, &gCallbacks, this); +} + +bool +StructuredCloneHelperInternal::Read(JSContext* aCx, + JS::MutableHandle aValue) +{ + MOZ_ASSERT(mBuffer, "Read() without Write() is not allowed."); + MOZ_ASSERT(!mShutdownCalled, "This method cannot be called after Shutdown."); + + bool ok = mBuffer->read(aCx, aValue, &gCallbacks, this); + mBuffer = nullptr; + return ok; +} + +bool +StructuredCloneHelperInternal::ReadTransferCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + JS::MutableHandleObject aReturnObject) +{ + MOZ_CRASH("Nothing to read."); + return false; +} + +bool +StructuredCloneHelperInternal::WriteTransferCallback(JSContext* aCx, + JS::Handle aObj, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + // No transfers are supported by default. + return false; +} + +void +StructuredCloneHelperInternal::FreeTransferCallback(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData) +{ + MOZ_CRASH("Nothing to free."); +} + +// StructuredCloneHelper class + +StructuredCloneHelper::StructuredCloneHelper(CloningSupport aSupportsCloning, + TransferringSupport aSupportsTransferring) + : mSupportsCloning(aSupportsCloning == CloningSupported) + , mSupportsTransferring(aSupportsTransferring == TransferringSupported) + , mParent(nullptr) +{} + +StructuredCloneHelper::~StructuredCloneHelper() +{ + Shutdown(); +} + +void +StructuredCloneHelper::Write(JSContext* aCx, + JS::Handle aValue, + ErrorResult& aRv) +{ + Write(aCx, aValue, JS::UndefinedHandleValue, aRv); +} + +void +StructuredCloneHelper::Write(JSContext* aCx, + JS::Handle aValue, + JS::Handle aTransfer, + ErrorResult& aRv) +{ + if (!StructuredCloneHelperInternal::Write(aCx, aValue, aTransfer)) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + } + + mTransferringPort.Clear(); +} + +void +StructuredCloneHelper::Read(nsISupports* aParent, + JSContext* aCx, + JS::MutableHandle aValue, + ErrorResult& aRv) +{ + mozilla::AutoRestore guard(mParent); + mParent = aParent; + + if (!StructuredCloneHelperInternal::Read(aCx, aValue)) { + JS_ClearPendingException(aCx); + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + } + + mBlobImplArray.Clear(); +} + +void +StructuredCloneHelper::ReadFromBuffer(nsISupports* aParent, + JSContext* aCx, + uint64_t* aBuffer, + size_t aBufferLength, + JS::MutableHandle aValue, + ErrorResult& aRv) +{ + MOZ_ASSERT(!mBuffer, "ReadFromBuffer() must be called without a Write()."); + MOZ_ASSERT(aBuffer); + + mozilla::AutoRestore guard(mParent); + mParent = aParent; + + if (!JS_ReadStructuredClone(aCx, aBuffer, aBufferLength, + JS_STRUCTURED_CLONE_VERSION, aValue, + &gCallbacks, this)) { + JS_ClearPendingException(aCx); + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + } +} + +void +StructuredCloneHelper::MoveBufferDataToArray(FallibleTArray& aArray, + ErrorResult& aRv) +{ + MOZ_ASSERT(mBuffer, "MoveBuffer() cannot be called without a Write()."); + + if (NS_WARN_IF(!aArray.SetLength(BufferSize(), mozilla::fallible))) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + + uint64_t* buffer; + size_t size; + mBuffer->steal(&buffer, &size); + mBuffer = nullptr; + + memcpy(aArray.Elements(), buffer, size); + js_free(buffer); +} + +void +StructuredCloneHelper::FreeBuffer(uint64_t* aBuffer, + size_t aBufferLength) +{ + MOZ_ASSERT(!mBuffer, "FreeBuffer() must be called without a Write()."); + MOZ_ASSERT(aBuffer); + MOZ_ASSERT(aBufferLength); + + JS_ClearStructuredClone(aBuffer, aBufferLength, &gCallbacks, this, false); +} + +JSObject* +StructuredCloneHelper::ReadCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + uint32_t aIndex) +{ + MOZ_ASSERT(mSupportsCloning); + + if (aTag == SCTAG_DOM_BLOB) { + MOZ_ASSERT(aIndex < mBlobImplArray.Length()); + nsRefPtr blobImpl = mBlobImplArray[aIndex]; + + // nsRefPtr needs to go out of scope before toObjectOrNull() is + // called because the static analysis thinks dereferencing XPCOM objects + // can GC (because in some cases it can!), and a return statement with a + // JSObject* type means that JSObject* is on the stack as a raw pointer + // while destructors are running. + JS::Rooted val(aCx); + { + nsRefPtr blob = Blob::Create(mParent, blobImpl); + if (!ToJSValue(aCx, blob, &val)) { + return nullptr; + } + } + + return &val.toObject(); + } + + if (aTag == SCTAG_DOM_FILELIST) { + JS::Rooted val(aCx); + { + nsRefPtr fileList = new FileList(mParent); + + // |aIndex| is the number of BlobImpls to use from |offset|. + uint32_t tag, offset; + if (!JS_ReadUint32Pair(aReader, &tag, &offset)) { + return nullptr; + } + MOZ_ASSERT(tag == 0); + + for (uint32_t i = 0; i < aIndex; ++i) { + uint32_t index = offset + i; + MOZ_ASSERT(index < mBlobImplArray.Length()); + + nsRefPtr blobImpl = mBlobImplArray[index]; + MOZ_ASSERT(blobImpl->IsFile()); + + nsRefPtr file = File::Create(mParent, blobImpl); + if (!fileList->Append(file)) { + return nullptr; + } + } + + if (!ToJSValue(aCx, fileList, &val)) { + return nullptr; + } + } + + return &val.toObject(); + } + + return NS_DOMReadStructuredClone(aCx, aReader, aTag, aIndex, nullptr); +} + +bool +StructuredCloneHelper::WriteCallback(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle aObj) +{ + if (!mSupportsCloning) { + return false; + } + + // See if this is a File/Blob object. + { + Blob* blob = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob))) { + BlobImpl* blobImpl = blob->Impl(); + if (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, + mBlobImplArray.Length())) { + mBlobImplArray.AppendElement(blobImpl); + return true; + } + + return false; + } + } + + { + FileList* fileList = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(FileList, aObj, fileList))) { + // A FileList is serialized writing the X number of elements and the offset + // from mBlobImplArray. The Read will take X elements from mBlobImplArray + // starting from the offset. + if (!JS_WriteUint32Pair(aWriter, SCTAG_DOM_FILELIST, + fileList->Length()) || + !JS_WriteUint32Pair(aWriter, 0, + mBlobImplArray.Length())) { + return false; + } + + for (uint32_t i = 0; i < fileList->Length(); ++i) { + mBlobImplArray.AppendElement(fileList->Item(i)->Impl()); + } + + return true; + } + } + + return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr); +} + +bool +StructuredCloneHelper::ReadTransferCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + JS::MutableHandleObject aReturnObject) +{ + MOZ_ASSERT(mSupportsTransferring); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + // This can be null. + nsCOMPtr window = do_QueryInterface(mParent); + + MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); + const MessagePortIdentifier& portIdentifier = mPortIdentifiers[aExtraData]; + + // aExtraData is the index of this port identifier. + ErrorResult rv; + nsRefPtr port = + MessagePort::Create(window, portIdentifier, rv); + if (NS_WARN_IF(rv.Failed())) { + return false; + } + + mTransferredPorts.AppendElement(port); + + JS::Rooted value(aCx); + if (!GetOrCreateDOMReflector(aCx, port, &value)) { + JS_ClearPendingException(aCx); + return false; + } + + aReturnObject.set(&value.toObject()); + return true; + } + + return false; +} + + +bool +StructuredCloneHelper::WriteTransferCallback(JSContext* aCx, + JS::Handle aObj, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) +{ + if (!mSupportsTransferring) { + return false; + } + + { + MessagePortBase* port = nullptr; + nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); + if (NS_SUCCEEDED(rv)) { + if (mTransferringPort.Contains(port)) { + // No duplicates. + return false; + } + + // We use aExtraData to store the index of this new port identifier. + *aExtraData = mPortIdentifiers.Length(); + MessagePortIdentifier* identifier = mPortIdentifiers.AppendElement(); + + if (!port->CloneAndDisentangle(*identifier)) { + return false; + } + + mTransferringPort.AppendElement(port); + + *aTag = SCTAG_DOM_MAP_MESSAGEPORT; + *aOwnership = JS::SCTAG_TMO_CUSTOM; + *aContent = nullptr; + + return true; + } + } + + return false; +} + +void +StructuredCloneHelper::FreeTransferCallback(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData) +{ + MOZ_ASSERT(mSupportsTransferring); + + if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { + MOZ_ASSERT(!aContent); + MOZ_ASSERT(aExtraData < mPortIdentifiers.Length()); + MessagePort::ForceClose(mPortIdentifiers[aExtraData]); + } +} + +} // dom namespace +} // mozilla namespace diff --git a/dom/base/StructuredCloneHelper.h b/dom/base/StructuredCloneHelper.h new file mode 100644 index 0000000000..0abdd190b8 --- /dev/null +++ b/dom/base/StructuredCloneHelper.h @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_StructuredCloneHelper_h +#define mozilla_dom_StructuredCloneHelper_h + +#include "js/StructuredClone.h" +#include "nsAutoPtr.h" +#include "nsISupports.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { + +class StructuredCloneHelperInternal +{ +public: + StructuredCloneHelperInternal(); + virtual ~StructuredCloneHelperInternal(); + + // These methods should be implemented in order to clone data. + // Read more documentation in js/public/StructuredClone.h. + + virtual JSObject* ReadCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + uint32_t aIndex) = 0; + + virtual bool WriteCallback(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle aObj) = 0; + + // This method has to be called when this object is not needed anymore. + // It will free memory and the buffer. This has to be called because + // otherwise the buffer will be freed in the DTOR of this class and at that + // point we cannot use the overridden methods. + void Shutdown(); + + // If these 3 methods are not implement, transfering objects will not be + // allowed. + + virtual bool + ReadTransferCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + JS::MutableHandleObject aReturnObject); + + virtual bool + WriteTransferCallback(JSContext* aCx, + JS::Handle aObj, + // Output: + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData); + + virtual void + FreeTransferCallback(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData); + + // These methods are what you should use. + + bool Write(JSContext* aCx, + JS::Handle aValue); + + bool Write(JSContext* aCx, + JS::Handle aValue, + JS::Handle aTransfer); + + bool Read(JSContext* aCx, + JS::MutableHandle aValue); + + uint64_t* BufferData() const + { + MOZ_ASSERT(mBuffer, "Write() has never been called."); + return mBuffer->data(); + } + + size_t BufferSize() const + { + MOZ_ASSERT(mBuffer, "Write() has never been called."); + return mBuffer->nbytes(); + } + +protected: + nsAutoPtr mBuffer; + +#ifdef DEBUG + bool mShutdownCalled; +#endif +}; + +class BlobImpl; +class MessagePortBase; +class MessagePortIdentifier; + +class StructuredCloneHelper : public StructuredCloneHelperInternal +{ +public: + enum CloningSupport + { + CloningSupported, + CloningNotSupported + }; + + enum TransferringSupport + { + TransferringSupported, + TransferringNotSupported + }; + + // If cloning is supported, this object will clone objects such as Blobs, + // FileList, ImageData, etc. + // If transferring is supported, we will transfer MessagePorts and in the + // future other transferrable objects. + explicit StructuredCloneHelper(CloningSupport aSupportsCloning, + TransferringSupport aSupportsTransferring); + virtual ~StructuredCloneHelper(); + + // Normally you should just use Write() and Read(). + + void Write(JSContext* aCx, + JS::Handle aValue, + ErrorResult &aRv); + + void Write(JSContext* aCx, + JS::Handle aValue, + JS::Handle aTransfer, + ErrorResult &aRv); + + void Read(nsISupports* aParent, + JSContext* aCx, + JS::MutableHandle aValue, + ErrorResult &aRv); + + // Sometimes, when IPC is involved, you must send a buffer after a Write(). + // This method 'steals' the internal data from this helper class. + // You should free this buffer with FreeBuffer(). + void MoveBufferDataToArray(FallibleTArray& aArray, + ErrorResult& aRv); + + // If you receive a buffer from IPC, you can use this method to retrieve a + // JS::Value. It can happen that you want to pre-populate the array of Blobs + // and/or the PortIdentifiers. + void ReadFromBuffer(nsISupports* aParent, + JSContext* aCx, + uint64_t* aBuffer, + size_t aBufferLength, + JS::MutableHandle aValue, + ErrorResult &aRv); + + // Use this method to free a buffer generated by MoveToBuffer(). + void FreeBuffer(uint64_t* aBuffer, + size_t aBufferLength); + + nsTArray>& BlobImpls() + { + MOZ_ASSERT(mSupportsCloning, "Blobs cannot be taken/set if cloning is not supported."); + return mBlobImplArray; + } + + const nsTArray>& GetTransferredPorts() const + { + MOZ_ASSERT(mSupportsTransferring); + return mTransferredPorts; + } + + nsTArray& PortIdentifiers() + { + MOZ_ASSERT(mSupportsTransferring); + return mPortIdentifiers; + } + + // Custom Callbacks + + virtual JSObject* ReadCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + uint32_t aIndex) override; + + virtual bool WriteCallback(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle aObj) override; + + virtual bool ReadTransferCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + void* aContent, + uint64_t aExtraData, + JS::MutableHandleObject aReturnObject) override; + + virtual bool WriteTransferCallback(JSContext* aCx, + JS::Handle aObj, + uint32_t* aTag, + JS::TransferableOwnership* aOwnership, + void** aContent, + uint64_t* aExtraData) override; + + virtual void FreeTransferCallback(uint32_t aTag, + JS::TransferableOwnership aOwnership, + void* aContent, + uint64_t aExtraData) override; +private: + bool mSupportsCloning; + bool mSupportsTransferring; + + // Useful for the structured clone algorithm: + + nsTArray> mBlobImplArray; + + // This raw pointer is set and unset into the ::Read(). It's always null + // outside that method. For this reason it's a raw pointer. + nsISupports* MOZ_NON_OWNING_REF mParent; + + // This hashtable contains the ports while doing write (transferring and + // mapping transferred objects to the objects in the clone). It's an empty + // array outside the 'Write()' method. + nsTArray> mTransferringPort; + + // This array contains the ports once we've finished the reading. It's + // generated from the mPortIdentifiers array. + nsTArray> mTransferredPorts; + + // This array contains the identifiers of the MessagePorts. Based on these we + // are able to reconnect the new transferred ports with the other + // MessageChannel ports. + nsTArray mPortIdentifiers; +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_StructuredCloneHelper_h diff --git a/dom/base/moz.build b/dom/base/moz.build index 36c5bf63e8..357965cd2f 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -195,6 +195,7 @@ EXPORTS.mozilla.dom += [ 'ScreenOrientation.h', 'ScriptSettings.h', 'ShadowRoot.h', + 'StructuredCloneHelper.h', 'StructuredCloneTags.h', 'StyleSheetList.h', 'SubtleCrypto.h', @@ -333,6 +334,7 @@ UNIFIED_SOURCES += [ 'SameProcessMessageQueue.cpp', 'ScriptSettings.cpp', 'ShadowRoot.cpp', + 'StructuredCloneHelper.cpp', 'StyleSheetList.cpp', 'SubtleCrypto.cpp', 'Text.cpp', diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 5df2dd30ef..8c03863994 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -221,6 +221,7 @@ #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" +#include "mozilla/dom/ImageBitmap.h" #ifdef HAVE_SIDEBAR #include "mozilla/dom/ExternalBinding.h" #endif @@ -8337,8 +8338,8 @@ nsGlobalWindow::PostMessageMozOuter(JSContext* aCx, JS::Handle aMessa JS::Rooted message(aCx, aMessage); JS::Rooted transfer(aCx, aTransfer); - if (!event->Write(aCx, message, transfer, this)) { - aError.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + event->Write(aCx, message, transfer, aError); + if (NS_WARN_IF(aError.Failed())) { return; } @@ -14426,3 +14427,18 @@ nsGlobalWindow::FireOnNewGlobalObject() #ifdef _WINDOWS_ #error "Never include windows.h in this file!" #endif + +already_AddRefed +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Nothing(), aRv); +} + +already_AddRefed +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv); +} diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index c12a512ded..60acc00cbe 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -52,6 +52,7 @@ #include "nsComponentManagerUtils.h" #include "nsSize.h" #include "nsCheapSets.h" +#include "mozilla/dom/ImageBitmapSource.h" #define DEFAULT_HOME_PAGE "www.mozilla.org" #define PREF_BROWSER_STARTUP_HOMEPAGE "browser.startup.homepage" @@ -1126,6 +1127,15 @@ public: GetContent(aCx, aRetval, aError); } + already_AddRefed + CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage, + mozilla::ErrorResult& aRv); + + already_AddRefed + CreateImageBitmap(const mozilla::dom::ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + mozilla::ErrorResult& aRv); + // ChromeWindow bits. Do NOT call these unless your window is in // fact an nsGlobalChromeWindow. uint16_t WindowState(); diff --git a/dom/base/test/test_postMessages.html b/dom/base/test/test_postMessages.html index 07c98011f7..17ced5463f 100644 --- a/dom/base/test/test_postMessages.html +++ b/dom/base/test/test_postMessages.html @@ -142,6 +142,24 @@ function runTests(obj) { }); }) + // non transfering tests + .then(function() { + if (obj.transferableObjects) { + return; + } + + // MessagePort + return new Promise(function(r, rr) { + var mc = new MessageChannel(); + obj.send(42, [mc.port1]).then(function(received) { + ok(false, "This object should not support port transferring"); + }, function() { + ok(true, "This object should not support port transferring"); + }) + .then(r); + }); + }) + // done. .then(function() { obj.finished(); @@ -170,7 +188,13 @@ function test_windowToWindow() { send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; - postMessage(what, '*', ports); + + try { + postMessage(what, '*', ports); + } catch(e) { + resolve = null; + rr(); + } }); }, @@ -217,7 +241,12 @@ function test_windowToIframeURL(url) { send: function(what, ports) { return new Promise(function(r, rr) { resolve = r; - ifr.contentWindow.postMessage(what, '*', ports); + try { + ifr.contentWindow.postMessage(what, '*', ports); + } catch(e) { + resolve = null; + rr(); + } }); }, @@ -231,12 +260,100 @@ function test_windowToIframeURL(url) { document.body.appendChild(ifr); } +// PostMessage for BroadcastChannel +function test_broadcastChannel() { + info("Testing broadcastChannel"); + + var bc1 = new BroadcastChannel('postMessagesTest'); + var bc2 = new BroadcastChannel('postMessagesTest'); + + var resolve; + + bc2.onmessage = function(e) { + if (!resolve) { + ok(false, "Unexpected message!"); + return; + } + + let tmp = resolve; + resolve = null; + tmp({ data: e.data, ports: [] }); + } + + runTests({ + clonableObjects: true, + transferableObjects: false, + send: function(what, ports) { + return new Promise(function(r, rr) { + if (ports.length) { + rr(); + return; + } + + resolve = r; + bc1.postMessage(what); + }); + }, + + finished: function() { + onmessage = null; + next(); + } + }); +} + +// PostMessage for MessagePort +function test_messagePort() { + info("Testing messagePort"); + + var mc = new MessageChannel(); + var resolve; + + mc.port2.onmessage = function(e) { + if (!resolve) { + ok(false, "Unexpected message!"); + return; + } + + let tmp = resolve; + resolve = null; + tmp({ data: e.data, ports: e.ports }); + } + + runTests({ + clonableObjects: true, + transferableObjects: true, + send: function(what, ports) { + return new Promise(function(r, rr) { + resolve = r; + try { + mc.port1.postMessage(what, ports); + } catch(e) { + resolve = null; + rr(); + } + }); + }, + + finished: function() { + onmessage = null; + next(); + } + }); +} + var tests = [ create_fileList, test_windowToWindow, test_windowToIframe, test_windowToCrossOriginIframe, + + test_broadcastChannel, + // TODO BroadcastChannel in worker + + test_messagePort, + // TODO MessagePort in worker ]; function next() { diff --git a/dom/broadcastchannel/BroadcastChannel.cpp b/dom/broadcastchannel/BroadcastChannel.cpp index e2d2a82591..bd0367249c 100644 --- a/dom/broadcastchannel/BroadcastChannel.cpp +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -8,7 +8,8 @@ #include "BroadcastChannelChild.h" #include "mozilla/dom/BroadcastChannelBinding.h" #include "mozilla/dom/Navigator.h" -#include "mozilla/dom/StructuredCloneUtils.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/StructuredCloneHelper.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/PBackgroundChild.h" @@ -31,20 +32,18 @@ namespace dom { using namespace workers; -class BroadcastChannelMessage final +class BroadcastChannelMessage final : public StructuredCloneHelper { public: NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage) - JSAutoStructuredCloneBuffer mBuffer; - StructuredCloneClosure mClosure; - BroadcastChannelMessage() - { } + : StructuredCloneHelper(CloningSupported, TransferringNotSupported) + {} private: ~BroadcastChannelMessage() - { } + {} }; namespace { @@ -167,13 +166,13 @@ public: ClonedMessageData message; SerializedStructuredCloneBuffer& buffer = message.data(); - buffer.data = mData->mBuffer.data(); - buffer.dataLength = mData->mBuffer.nbytes(); + buffer.data = mData->BufferData(); + buffer.dataLength = mData->BufferSize(); PBackgroundChild* backgroundManager = mActor->Manager(); MOZ_ASSERT(backgroundManager); - const nsTArray>& blobImpls = mData->mClosure.mBlobImpls; + const nsTArray>& blobImpls = mData->BlobImpls(); if (!blobImpls.IsEmpty()) { message.blobsChild().SetCapacity(blobImpls.Length()); @@ -498,12 +497,12 @@ BroadcastChannel::PostMessageInternal(JSContext* aCx, { nsRefPtr data = new BroadcastChannelMessage(); - if (!WriteStructuredClone(aCx, aMessage, data->mBuffer, data->mClosure)) { - aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + data->Write(aCx, aMessage, aRv); + if (NS_WARN_IF(aRv.Failed())) { return; } - const nsTArray>& blobImpls = data->mClosure.mBlobImpls; + const nsTArray>& blobImpls = data->BlobImpls(); for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { if (!blobImpls[i]->MayBeClonedToOtherThreads()) { aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); diff --git a/dom/broadcastchannel/BroadcastChannelChild.cpp b/dom/broadcastchannel/BroadcastChannelChild.cpp index 3c4c4aac84..892b4c8e30 100644 --- a/dom/broadcastchannel/BroadcastChannelChild.cpp +++ b/dom/broadcastchannel/BroadcastChannelChild.cpp @@ -11,7 +11,7 @@ #include "mozilla/dom/File.h" #include "mozilla/dom/MessageEvent.h" #include "mozilla/dom/MessageEventBinding.h" -#include "mozilla/dom/StructuredCloneUtils.h" +#include "mozilla/dom/StructuredCloneHelper.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerScope.h" #include "mozilla/dom/ScriptSettings.h" @@ -86,17 +86,20 @@ BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData) } JSContext* cx = jsapi.cx(); - const SerializedStructuredCloneBuffer& buffer = aData.data(); - StructuredCloneData cloneData; - cloneData.mData = buffer.data; - cloneData.mDataLength = buffer.dataLength; - cloneData.mClosure.mBlobImpls.SwapElements(blobs); + StructuredCloneHelper cloneHelper(StructuredCloneHelper::CloningSupported, + StructuredCloneHelper::TransferringNotSupported); + + cloneHelper.BlobImpls().AppendElements(blobs); JS::Rooted value(cx, JS::NullValue()); - if (cloneData.mDataLength && !ReadStructuredClone(cx, cloneData, &value)) { - JS_ClearPendingException(cx); - return false; + if (buffer.dataLength) { + ErrorResult rv; + cloneHelper.ReadFromBuffer(mBC->GetParentObject(), cx, + buffer.data, buffer.dataLength, &value, rv); + if (NS_WARN_IF(rv.Failed())) { + return true; + } } RootedDictionary init(cx); diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp index 52a9dba823..380c4e5fb5 100644 --- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -78,6 +78,7 @@ #include "mozilla/CheckedInt.h" #include "mozilla/DebugOnly.h" #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ImageData.h" #include "mozilla/dom/PBrowserParent.h" #include "mozilla/dom/ToJSValue.h" @@ -2007,7 +2008,7 @@ CanvasRenderingContext2D::CreateRadialGradient(double x0, double y0, double r0, } already_AddRefed -CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, +CanvasRenderingContext2D::CreatePattern(const CanvasImageSource& source, const nsAString& repeat, ErrorResult& error) { @@ -2028,8 +2029,8 @@ CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& e } Element* htmlElement; - if (element.IsHTMLCanvasElement()) { - HTMLCanvasElement* canvas = &element.GetAsHTMLCanvasElement(); + if (source.IsHTMLCanvasElement()) { + HTMLCanvasElement* canvas = &source.GetAsHTMLCanvasElement(); htmlElement = canvas; nsIntSize size = canvas->GetSize(); @@ -2049,16 +2050,29 @@ CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& e return pat.forget(); } - } else if (element.IsHTMLImageElement()) { - HTMLImageElement* img = &element.GetAsHTMLImageElement(); + } else if (source.IsHTMLImageElement()) { + HTMLImageElement* img = &source.GetAsHTMLImageElement(); if (img->IntrinsicState().HasState(NS_EVENT_STATE_BROKEN)) { error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr; } htmlElement = img; + } else if (source.IsHTMLVideoElement()) { + htmlElement = &source.GetAsHTMLVideoElement(); } else { - htmlElement = &element.GetAsHTMLVideoElement(); + // Special case for ImageBitmap + ImageBitmap& imgBitmap = source.GetAsImageBitmap(); + EnsureTarget(); + RefPtr srcSurf = imgBitmap.PrepareForDrawTarget(mTarget); + + // An ImageBitmap never taints others so we set principalForSecurityCheck to + // nullptr and set CORSUsed to true for passing the security check in + // CanvasUtils::DoDrawImageSecurityCheck(). + nsRefPtr pat = + new CanvasPattern(this, srcSurf, repeatMode, nullptr, false, true); + + return pat.forget(); } EnsureTarget(); @@ -4332,7 +4346,7 @@ CanvasRenderingContext2D::CachedSurfaceFromElement(Element* aElement) // are all passed in. void -CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image, +CanvasRenderingContext2D::DrawImage(const CanvasImageSource& image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh, @@ -4366,7 +4380,17 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image error.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } - } else { + } else if (image.IsImageBitmap()) { + ImageBitmap& imageBitmap = image.GetAsImageBitmap(); + srcSurf = imageBitmap.PrepareForDrawTarget(mTarget); + + if (!srcSurf) { + return; + } + + imgSize = gfx::IntSize(imageBitmap.Width(), imageBitmap.Height()); + } + else { if (image.IsHTMLImageElement()) { HTMLImageElement* img = &image.GetAsHTMLImageElement(); element = img; diff --git a/dom/canvas/CanvasRenderingContext2D.h b/dom/canvas/CanvasRenderingContext2D.h index 3c794b3957..dc2627521a 100644 --- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -38,7 +38,8 @@ class SourceSurface; } // namespace gl namespace dom { -class HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement; +class HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap; +typedef HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElementOrImageBitmap CanvasImageSource; class ImageData; class StringOrCanvasGradientOrCanvasPattern; class OwningStringOrCanvasGradientOrCanvasPattern; @@ -126,9 +127,6 @@ class CanvasRenderingContext2D final : public nsICanvasRenderingContextInternal, public nsWrapperCache { -typedef HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement - HTMLImageOrCanvasOrVideoElement; - virtual ~CanvasRenderingContext2D(); public: @@ -198,7 +196,7 @@ public: CreateRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1, ErrorResult& aError); already_AddRefed - CreatePattern(const HTMLImageOrCanvasOrVideoElement& element, + CreatePattern(const CanvasImageSource& element, const nsAString& repeat, ErrorResult& error); double ShadowOffsetX() @@ -274,20 +272,20 @@ public: void RemoveHitRegion(const nsAString& id); void ClearHitRegions(); - void DrawImage(const HTMLImageOrCanvasOrVideoElement& image, + void DrawImage(const CanvasImageSource& image, double dx, double dy, mozilla::ErrorResult& error) { DrawImage(image, 0.0, 0.0, 0.0, 0.0, dx, dy, 0.0, 0.0, 0, error); } - void DrawImage(const HTMLImageOrCanvasOrVideoElement& image, + void DrawImage(const CanvasImageSource& image, double dx, double dy, double dw, double dh, mozilla::ErrorResult& error) { DrawImage(image, 0.0, 0.0, 0.0, 0.0, dx, dy, dw, dh, 2, error); } - void DrawImage(const HTMLImageOrCanvasOrVideoElement& image, + void DrawImage(const CanvasImageSource& image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh, mozilla::ErrorResult& error) { @@ -741,7 +739,7 @@ protected: nsLayoutUtils::SurfaceFromElementResult CachedSurfaceFromElement(Element* aElement); - void DrawImage(const HTMLImageOrCanvasOrVideoElement &imgElt, + void DrawImage(const CanvasImageSource &imgElt, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh, uint8_t optional_argc, mozilla::ErrorResult& error); diff --git a/dom/canvas/CanvasUtils.cpp b/dom/canvas/CanvasUtils.cpp index 41ff889c9d..04fec40e8c 100644 --- a/dom/canvas/CanvasUtils.cpp +++ b/dom/canvas/CanvasUtils.cpp @@ -29,14 +29,19 @@ using namespace mozilla::gfx; namespace mozilla { namespace CanvasUtils { +/** + * This security check utility might be called from an source that never taints + * others. For example, while painting a CanvasPattern, which is created from an + * ImageBitmap, onto a canvas. In this case, the caller could set the CORSUsed + * true in order to pass this check and leave the aPrincipal to be a nullptr + * since the aPrincipal is not going to be used. + */ void DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement, nsIPrincipal *aPrincipal, bool forceWriteOnly, bool CORSUsed) { - NS_PRECONDITION(aPrincipal, "Must have a principal here"); - // Callers should ensure that mCanvasElement is non-null before calling this if (!aCanvasElement) { NS_WARNING("DoDrawImageSecurityCheck called without canvas element!"); @@ -56,6 +61,8 @@ DoDrawImageSecurityCheck(dom::HTMLCanvasElement *aCanvasElement, if (CORSUsed) return; + NS_PRECONDITION(aPrincipal, "Must have a principal here"); + if (aCanvasElement->NodePrincipal()->Subsumes(aPrincipal)) { // This canvas has access to that image anyway return; diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp new file mode 100644 index 0000000000..b6466864f1 --- /dev/null +++ b/dom/canvas/ImageBitmap.cpp @@ -0,0 +1,1090 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ImageBitmap.h" +#include "mozilla/dom/ImageBitmapBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/gfx/2D.h" +#include "imgTools.h" +#include "libyuv.h" +#include "nsLayoutUtils.h" + +using namespace mozilla::gfx; +using namespace mozilla::layers; + +namespace mozilla { +namespace dom { + +using namespace workers; + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ImageBitmap, mParent) +NS_IMPL_CYCLE_COLLECTING_ADDREF(ImageBitmap) +NS_IMPL_CYCLE_COLLECTING_RELEASE(ImageBitmap) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImageBitmap) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +/* + * This helper function copies the data of the given DataSourceSurface, + * _aSurface_, in the given area, _aCropRect_, into a new DataSourceSurface. + * This might return null if it can not create a new SourceSurface or it cannot + * read data from the given _aSurface_. + */ +static already_AddRefed +CropDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect) +{ + MOZ_ASSERT(aSurface); + + // Calculate the size of the new SourceSurface. + const SurfaceFormat format = aSurface->GetFormat(); + const IntSize dstSize = gfx::IntSize(aCropRect.width, aCropRect.height); + const uint32_t dstStride = dstSize.width * BytesPerPixel(format); + + // Create a new SourceSurface. + RefPtr dstDataSurface = + Factory::CreateDataSourceSurfaceWithStride(dstSize, format, dstStride); + + if (NS_WARN_IF(!dstDataSurface)) { + return nullptr; + } + + // Copy the raw data into the newly created DataSourceSurface. + RefPtr srcDataSurface = aSurface; + DataSourceSurface::MappedSurface srcMap; + DataSourceSurface::MappedSurface dstMap; + if (NS_WARN_IF(!srcDataSurface->Map(DataSourceSurface::MapType::READ, &srcMap)) || + NS_WARN_IF(!dstDataSurface->Map(DataSourceSurface::MapType::WRITE, &dstMap))) { + return nullptr; + } + + uint8_t* srcBufferPtr = srcMap.mData + aCropRect.y * srcMap.mStride + + aCropRect.x * BytesPerPixel(format); + uint8_t* dstBufferPtr = dstMap.mData; + for (int i = 0; i < dstSize.height; ++i) { + memcpy(dstBufferPtr, srcBufferPtr, dstMap.mStride); + srcBufferPtr += srcMap.mStride; + dstBufferPtr += dstMap.mStride; + } + + srcDataSurface->Unmap(); + dstDataSurface->Unmap(); + + return dstDataSurface.forget(); +} + +/* + * Encapsulate the given _aSurface_ into a layers::CairoImage. + */ +static already_AddRefed +CreateImageFromSurface(SourceSurface* aSurface, ErrorResult& aRv) +{ + MOZ_ASSERT(aSurface); + + layers::CairoImage::Data cairoData; + cairoData.mSize = aSurface->GetSize(); + cairoData.mSourceSurface = aSurface; + + nsRefPtr image = new layers::CairoImage(); + + image->SetData(cairoData); + + return image.forget(); +} + +/* + * CreateImageFromRawData(), CreateSurfaceFromRawData() and + * CreateImageFromRawDataInMainThreadSyncTask are helpers for + * create-from-ImageData case + */ +static already_AddRefed +CreateSurfaceFromRawData(const gfx::IntSize& aSize, + uint32_t aStride, + gfx::SurfaceFormat aFormat, + uint8_t* aBuffer, + uint32_t aBufferLength, + const Maybe& aCropRect, + ErrorResult& aRv) +{ + MOZ_ASSERT(!aSize.IsEmpty()); + MOZ_ASSERT(aBuffer); + + // Wrap the source buffer into a SourceSurface. + RefPtr dataSurface = + Factory::CreateWrappingDataSourceSurface(aBuffer, aStride, aSize, aFormat); + + if (NS_WARN_IF(!dataSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // The temporary cropRect variable is equal to the size of source buffer if we + // do not need to crop, or it equals to the given cropping size. + const IntRect cropRect = aCropRect.valueOr(IntRect(0, 0, aSize.width, aSize.height)); + + // Copy the source buffer in the _cropRect_ area into a new SourceSurface. + RefPtr result = CropDataSourceSurface(dataSurface, cropRect); + + if (NS_WARN_IF(!result)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + return result.forget(); +} + +static already_AddRefed +CreateImageFromRawData(const gfx::IntSize& aSize, + uint32_t aStride, + gfx::SurfaceFormat aFormat, + uint8_t* aBuffer, + uint32_t aBufferLength, + const Maybe& aCropRect, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Copy and crop the source buffer into a SourceSurface. + RefPtr rgbaSurface = + CreateSurfaceFromRawData(aSize, aStride, aFormat, + aBuffer, aBufferLength, + aCropRect, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Convert RGBA to BGRA + RefPtr rgbaDataSurface = rgbaSurface->GetDataSurface(); + RefPtr bgraDataSurface = + Factory::CreateDataSourceSurfaceWithStride(rgbaDataSurface->GetSize(), + SurfaceFormat::B8G8R8A8, + rgbaDataSurface->Stride()); + + DataSourceSurface::MappedSurface rgbaMap; + DataSourceSurface::MappedSurface bgraMap; + + if (NS_WARN_IF(!rgbaDataSurface->Map(DataSourceSurface::MapType::READ, &rgbaMap)) || + NS_WARN_IF(!bgraDataSurface->Map(DataSourceSurface::MapType::WRITE, &bgraMap))) { + return nullptr; + } + + libyuv::ABGRToARGB(rgbaMap.mData, rgbaMap.mStride, + bgraMap.mData, bgraMap.mStride, + bgraDataSurface->GetSize().width, + bgraDataSurface->GetSize().height); + + rgbaDataSurface->Unmap(); + bgraDataSurface->Unmap(); + + // Create an Image from the BGRA SourceSurface. + nsRefPtr image = CreateImageFromSurface(bgraDataSurface, aRv); + + return image.forget(); +} + +/* + * This is a synchronous task. + * This class is used to create a layers::CairoImage from raw data in the main + * thread. While creating an ImageBitmap from an ImageData, we need to create + * a SouceSurface from the ImageData's raw data and then set the SourceSurface + * into a layers::CairoImage. However, the layers::CairoImage asserts the + * setting operation in the main thread, so if we are going to create an + * ImageBitmap from an ImageData off the main thread, we post an event to the + * main thread to create a layers::CairoImage from an ImageData's raw data. + */ +class CreateImageFromRawDataInMainThreadSyncTask final : + public WorkerMainThreadRunnable +{ +public: + CreateImageFromRawDataInMainThreadSyncTask(uint8_t* aBuffer, + uint32_t aBufferLength, + uint32_t aStride, + gfx::SurfaceFormat aFormat, + const gfx::IntSize& aSize, + const Maybe& aCropRect, + ErrorResult& aError, + layers::Image** aImage) + : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate()) + , mImage(aImage) + , mBuffer(aBuffer) + , mBufferLength(aBufferLength) + , mStride(aStride) + , mFormat(aFormat) + , mSize(aSize) + , mCropRect(aCropRect) + , mError(aError) + { + } + + bool MainThreadRun() override + { + nsRefPtr image = + CreateImageFromRawData(mSize, mStride, mFormat, + mBuffer, mBufferLength, + mCropRect, + mError); + + if (NS_WARN_IF(mError.Failed())) { + return false; + } + + image.forget(mImage); + + return true; + } + +private: + layers::Image** mImage; + uint8_t* mBuffer; + uint32_t mBufferLength; + uint32_t mStride; + gfx::SurfaceFormat mFormat; + gfx::IntSize mSize; + const Maybe& mCropRect; + ErrorResult& mError; +}; + +static bool +CheckSecurityForHTMLElements(bool aIsWriteOnly, bool aCORSUsed, nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(aPrincipal); + + if (aIsWriteOnly) { + return false; + } + + if (!aCORSUsed) { + nsIGlobalObject* incumbentSettingsObject = GetIncumbentGlobal(); + if (NS_WARN_IF(!incumbentSettingsObject)) { + return false; + } + + nsIPrincipal* principal = incumbentSettingsObject->PrincipalOrNull(); + if (NS_WARN_IF(!principal) || !(principal->Subsumes(aPrincipal))) { + return false; + } + } + + return true; +} + +static bool +CheckSecurityForHTMLElements(const nsLayoutUtils::SurfaceFromElementResult& aRes) +{ + return CheckSecurityForHTMLElements(aRes.mIsWriteOnly, aRes.mCORSUsed, aRes.mPrincipal); +} + +/* + * A wrapper to the nsLayoutUtils::SurfaceFromElement() function followed by the + * security checking. + */ +template +static already_AddRefed +GetSurfaceFromElement(nsIGlobalObject* aGlobal, HTMLElementType& aElement, ErrorResult& aRv) +{ + nsLayoutUtils::SurfaceFromElementResult res = + nsLayoutUtils::SurfaceFromElement(&aElement, nsLayoutUtils::SFE_WANT_FIRST_FRAME); + + // check origin-clean + if (!CheckSecurityForHTMLElements(res)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + if (NS_WARN_IF(!res.mSourceSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + RefPtr surface(res.mSourceSurface); + return surface.forget(); +} + +/* + * The specification doesn't allow to create an ImegeBitmap from a vector image. + * This function is used to check if the given HTMLImageElement contains a + * raster image. + */ +static bool +HasRasterImage(HTMLImageElement& aImageEl) +{ + nsresult rv; + + nsCOMPtr imgRequest; + rv = aImageEl.GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, + getter_AddRefs(imgRequest)); + if (NS_SUCCEEDED(rv) && imgRequest) { + nsCOMPtr imgContainer; + rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); + if (NS_SUCCEEDED(rv) && imgContainer && + imgContainer->GetType() == imgIContainer::TYPE_RASTER) { + return true; + } + } + + return false; +} + +ImageBitmap::ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData) + : mParent(aGlobal) + , mData(aData) + , mSurface(nullptr) + , mPictureRect(0, 0, aData->GetSize().width, aData->GetSize().height) +{ + MOZ_ASSERT(aData, "aData is null in ImageBitmap constructor."); +} + +ImageBitmap::~ImageBitmap() +{ +} + +JSObject* +ImageBitmap::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return ImageBitmapBinding::Wrap(aCx, this, aGivenProto); +} + +void +ImageBitmap::SetPictureRect(const IntRect& aRect, ErrorResult& aRv) +{ + gfx::IntRect rect = aRect; + + // fix up negative dimensions + if (rect.width < 0) { + CheckedInt32 checkedX = CheckedInt32(rect.x) + rect.width; + + if (!checkedX.isValid()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + rect.x = checkedX.value(); + rect.width = -(rect.width); + } + + if (rect.height < 0) { + CheckedInt32 checkedY = CheckedInt32(rect.y) + rect.height; + + if (!checkedY.isValid()) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return; + } + + rect.y = checkedY.value(); + rect.height = -(rect.height); + } + + mPictureRect = rect; +} + +already_AddRefed +ImageBitmap::PrepareForDrawTarget(gfx::DrawTarget* aTarget) +{ + MOZ_ASSERT(aTarget); + + if (!mSurface) { + mSurface = mData->GetAsSourceSurface(); + } + + if (!mSurface) { + return nullptr; + } + + RefPtr target = aTarget; + IntRect surfRect(0, 0, mSurface->GetSize().width, mSurface->GetSize().height); + + // Check if we still need to crop our surface + if (!mPictureRect.IsEqualEdges(surfRect)) { + + IntRect surfPortion = surfRect.Intersect(mPictureRect); + + // the crop lies entirely outside the surface area, nothing to draw + if (surfPortion.IsEmpty()) { + mSurface = nullptr; + RefPtr surface(mSurface); + return surface.forget(); + } + + IntPoint dest(std::max(0, surfPortion.X() - mPictureRect.X()), + std::max(0, surfPortion.Y() - mPictureRect.Y())); + + // Do not initialize this target with mPictureRect.Size(). + // In the Windows8 D2D1 backend, it might trigger "partial upload" from a + // non-SourceSurfaceD2D1 surface to a D2D1Image in the following + // CopySurface() step. However, the "partial upload" only supports uploading + // a rectangle starts from the upper-left point, which means it cannot + // upload an arbitrary part of the source surface and this causes problems + // if the mPictureRect is not starts from the upper-left point. + target = target->CreateSimilarDrawTarget(mSurface->GetSize(), + target->GetFormat()); + + if (!target) { + mSurface = nullptr; + RefPtr surface(mSurface); + return surface.forget(); + } + + // Make mCropRect match new surface we've cropped to + mPictureRect.MoveTo(0, 0); + target->CopySurface(mSurface, surfPortion, dest); + mSurface = target->Snapshot(); + } + + // Replace our surface with one optimized for the target we're about to draw + // to, under the assumption it'll likely be drawn again to that target. + // This call should be a no-op for already-optimized surfaces + mSurface = target->OptimizeSourceSurface(mSurface); + + RefPtr surface(mSurface); + return surface.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Check if the image element is completely available or not. + if (!aImageEl.Complete()) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Check if the image element is a bitmap (e.g. it's a vector graphic) or not. + if (!HasRasterImage(aImageEl)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Get the SourceSurface out from the image element and then do security + // checking. + RefPtr surface = GetSurfaceFromElement(aGlobal, aImageEl, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Create ImageBitmap. + nsRefPtr data = CreateImageFromSurface(surface, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Check network state. + if (aVideoEl.NetworkState() == HTMLMediaElement::NETWORK_EMPTY) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Check ready state. + // Cannot be HTMLMediaElement::HAVE_NOTHING or HTMLMediaElement::HAVE_METADATA. + if (aVideoEl.ReadyState() <= HTMLMediaElement::HAVE_METADATA) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Check security. + nsCOMPtr principal = aVideoEl.GetCurrentPrincipal(); + bool CORSUsed = aVideoEl.GetCORSMode() != CORS_NONE; + if (!CheckSecurityForHTMLElements(false, CORSUsed, principal)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + // Create ImageBitmap. + ImageContainer *container = aVideoEl.GetImageContainer(); + + if (!container) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + AutoLockImage lockImage(container); + layers::Image* data = lockImage.GetImage(); + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl, + const Maybe& aCropRect, ErrorResult& aRv) +{ + if (aCanvasEl.Width() == 0 || aCanvasEl.Height() == 0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + RefPtr surface = GetSurfaceFromElement(aGlobal, aCanvasEl, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Crop the source surface if needed. + RefPtr croppedSurface; + IntRect cropRect = aCropRect.valueOr(IntRect()); + + // If the HTMLCanvasElement's rendering context is WebGL, then the snapshot + // we got from the HTMLCanvasElement is a DataSourceSurface which is a copy + // of the rendering context. We handle cropping in this case. + if ((aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL1 || + aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) && + aCropRect.isSome()) { + // The _surface_ must be a DataSourceSurface. + MOZ_ASSERT(surface->GetType() == SurfaceType::DATA, + "The snapshot SourceSurface from WebGL rendering contest is not \ + DataSourceSurface."); + RefPtr dataSurface = surface->GetDataSurface(); + croppedSurface = CropDataSourceSurface(dataSurface, cropRect); + cropRect.MoveTo(0, 0); + } + else { + croppedSurface = surface; + } + + if (NS_WARN_IF(!croppedSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // Create an Image from the SourceSurface. + nsRefPtr data = CreateImageFromSurface(croppedSurface, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(cropRect, aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Copy data into SourceSurface. + dom::Uint8ClampedArray array; + DebugOnly inited = array.Init(aImageData.GetDataObject()); + MOZ_ASSERT(inited); + + array.ComputeLengthAndData(); + const SurfaceFormat FORMAT = SurfaceFormat::R8G8B8A8; + const uint32_t BYTES_PER_PIXEL = BytesPerPixel(FORMAT); + const uint32_t imageWidth = aImageData.Width(); + const uint32_t imageHeight = aImageData.Height(); + const uint32_t imageStride = imageWidth * BYTES_PER_PIXEL; + const uint32_t dataLength = array.Length(); + const gfx::IntSize imageSize(imageWidth, imageHeight); + + // Check the ImageData is neutered or not. + if (imageWidth == 0 || imageHeight == 0 || + (imageWidth * imageHeight * BYTES_PER_PIXEL) != dataLength) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + // Create and Crop the raw data into a layers::Image + nsRefPtr data; + if (NS_IsMainThread()) { + data = CreateImageFromRawData(imageSize, imageStride, FORMAT, + array.Data(), dataLength, + aCropRect, aRv); + } else { + nsRefPtr task + = new CreateImageFromRawDataInMainThreadSyncTask(array.Data(), + dataLength, + imageStride, + FORMAT, + imageSize, + aCropRect, + aRv, + getter_AddRefs(data)); + task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Create an ImageBimtap. + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // The cropping information has been handled in the CreateImageFromRawData() + // function. + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx, + const Maybe& aCropRect, ErrorResult& aRv) +{ + // Check origin-clean. + if (aCanvasCtx.GetCanvas()->IsWriteOnly()) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + RefPtr surface = aCanvasCtx.GetSurfaceSnapshot(); + + if (NS_WARN_IF(!surface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + const IntSize surfaceSize = surface->GetSize(); + if (surfaceSize.width == 0 || surfaceSize.height == 0) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + nsRefPtr data = CreateImageFromSurface(surface, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +/* static */ already_AddRefed +ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap, + const Maybe& aCropRect, ErrorResult& aRv) +{ + if (!aImageBitmap.mData) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + nsRefPtr data = aImageBitmap.mData; + nsRefPtr ret = new ImageBitmap(aGlobal, data); + + // Set the picture rectangle. + if (ret && aCropRect.isSome()) { + ret->SetPictureRect(aCropRect.ref(), aRv); + } + + return ret.forget(); +} + +class FulfillImageBitmapPromise +{ +protected: + FulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap) + : mPromise(aPromise) + , mImageBitmap(aImageBitmap) + { + MOZ_ASSERT(aPromise); + } + + void DoFulfillImageBitmapPromise() + { + mPromise->MaybeResolve(mImageBitmap); + } + +private: + nsRefPtr mPromise; + nsRefPtr mImageBitmap; +}; + +class FulfillImageBitmapPromiseTask final : public nsRunnable, + public FulfillImageBitmapPromise +{ +public: + FulfillImageBitmapPromiseTask(Promise* aPromise, ImageBitmap* aImageBitmap) + : FulfillImageBitmapPromise(aPromise, aImageBitmap) + { + } + + NS_IMETHOD Run() override + { + DoFulfillImageBitmapPromise(); + return NS_OK; + } +}; + +class FulfillImageBitmapPromiseWorkerTask final : public WorkerSameThreadRunnable, + public FulfillImageBitmapPromise +{ +public: + FulfillImageBitmapPromiseWorkerTask(Promise* aPromise, ImageBitmap* aImageBitmap) + : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()), + FulfillImageBitmapPromise(aPromise, aImageBitmap) + { + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + DoFulfillImageBitmapPromise(); + return true; + } +}; + +static void +AsyncFulfillImageBitmapPromise(Promise* aPromise, ImageBitmap* aImageBitmap) +{ + if (NS_IsMainThread()) { + nsCOMPtr task = + new FulfillImageBitmapPromiseTask(aPromise, aImageBitmap); + NS_DispatchToCurrentThread(task); // Actually, to the main-thread. + } else { + nsRefPtr task = + new FulfillImageBitmapPromiseWorkerTask(aPromise, aImageBitmap); + task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); // Actually, to the current worker-thread. + } +} + +static already_AddRefed +DecodeBlob(Blob& aBlob, ErrorResult& aRv) +{ + // Get the internal stream of the blob. + nsCOMPtr stream; + aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Get the MIME type string of the blob. + // The type will be checked in the DecodeImage() method. + nsAutoString mimeTypeUTF16; + aBlob.GetType(mimeTypeUTF16); + + // Get the Component object. + nsCOMPtr imgtool = do_GetService(NS_IMGTOOLS_CID); + if (NS_WARN_IF(!imgtool)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // Decode image. + NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeTypeUTF16); // NS_ConvertUTF16toUTF8 ---|> nsAutoCString + nsCOMPtr imgContainer; + nsresult rv = imgtool->DecodeImage(stream, mimeTypeUTF8, getter_AddRefs(imgContainer)); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + // Get the surface out. + uint32_t frameFlags = imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_WANT_DATA_SURFACE; + uint32_t whichFrame = imgIContainer::FRAME_FIRST; + RefPtr surface = imgContainer->GetFrame(whichFrame, frameFlags); + + if (NS_WARN_IF(!surface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + return surface.forget(); +} + +static already_AddRefed +DecodeAndCropBlob(Blob& aBlob, Maybe& aCropRect, ErrorResult& aRv) +{ + // Decode the blob into a SourceSurface. + RefPtr surface = DecodeBlob(aBlob, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Crop the source surface if needed. + RefPtr croppedSurface = surface; + + if (aCropRect.isSome()) { + // The blob is just decoded into a RasterImage and not optimized yet, so the + // _surface_ we get is a DataSourceSurface which wraps the RasterImage's + // raw buffer. + MOZ_ASSERT(surface->GetType() == SurfaceType::DATA, + "The SourceSurface from just decoded Blob is not DataSourceSurface."); + RefPtr dataSurface = surface->GetDataSurface(); + croppedSurface = CropDataSourceSurface(dataSurface, aCropRect.ref()); + aCropRect->MoveTo(0, 0); + } + + if (NS_WARN_IF(!croppedSurface)) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // Create an Image from the source surface. + nsRefPtr image = CreateImageFromSurface(croppedSurface, aRv); + + return image.forget(); +} + +class CreateImageBitmapFromBlob +{ +protected: + CreateImageBitmapFromBlob(Promise* aPromise, + nsIGlobalObject* aGlobal, + Blob& aBlob, + const Maybe& aCropRect) + : mPromise(aPromise), + mGlobalObject(aGlobal), + mBlob(&aBlob), + mCropRect(aCropRect) + { + } + + virtual ~CreateImageBitmapFromBlob() + { + } + + void DoCreateImageBitmapFromBlob(ErrorResult& aRv) + { + nsRefPtr imageBitmap = CreateImageBitmap(aRv); + + // handle errors while creating ImageBitmap + // (1) error occurs during reading of the object + // (2) the image data is not in a supported file format + // (3) the image data is corrupted + // All these three cases should reject promise with null value + if (aRv.Failed()) { + mPromise->MaybeReject(aRv); + return; + } + + if (imageBitmap && mCropRect.isSome()) { + imageBitmap->SetPictureRect(mCropRect.ref(), aRv); + + if (aRv.Failed()) { + mPromise->MaybeReject(aRv); + return; + } + } + + mPromise->MaybeResolve(imageBitmap); + return; + } + + virtual already_AddRefed CreateImageBitmap(ErrorResult& aRv) = 0; + + nsRefPtr mPromise; + nsCOMPtr mGlobalObject; + RefPtr mBlob; + Maybe mCropRect; +}; + +class CreateImageBitmapFromBlobTask final : public nsRunnable, + public CreateImageBitmapFromBlob +{ +public: + CreateImageBitmapFromBlobTask(Promise* aPromise, + nsIGlobalObject* aGlobal, + Blob& aBlob, + const Maybe& aCropRect) + :CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect) + { + } + + NS_IMETHOD Run() override + { + ErrorResult error; + DoCreateImageBitmapFromBlob(error); + return NS_OK; + } + +private: + already_AddRefed CreateImageBitmap(ErrorResult& aRv) override + { + nsRefPtr data = DecodeAndCropBlob(*mBlob, mCropRect, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // Create ImageBitmap object. + nsRefPtr imageBitmap = new ImageBitmap(mGlobalObject, data); + return imageBitmap.forget(); + } +}; + +class CreateImageBitmapFromBlobWorkerTask final : public WorkerSameThreadRunnable, + public CreateImageBitmapFromBlob +{ + // This is a synchronous task. + class DecodeBlobInMainThreadSyncTask final : public WorkerMainThreadRunnable + { + public: + DecodeBlobInMainThreadSyncTask(WorkerPrivate* aWorkerPrivate, + Blob& aBlob, + Maybe& aCropRect, + ErrorResult& aError, + layers::Image** aImage) + : WorkerMainThreadRunnable(aWorkerPrivate) + , mBlob(aBlob) + , mCropRect(aCropRect) + , mError(aError) + , mImage(aImage) + { + } + + bool MainThreadRun() override + { + nsRefPtr image = DecodeAndCropBlob(mBlob, mCropRect, mError); + + if (NS_WARN_IF(mError.Failed())) { + return false; + } + + image.forget(mImage); + + return true; + } + + private: + Blob& mBlob; + Maybe& mCropRect; + ErrorResult& mError; + layers::Image** mImage; + }; + +public: + CreateImageBitmapFromBlobWorkerTask(Promise* aPromise, + nsIGlobalObject* aGlobal, + mozilla::dom::Blob& aBlob, + const Maybe& aCropRect) + : WorkerSameThreadRunnable(GetCurrentThreadWorkerPrivate()), + CreateImageBitmapFromBlob(aPromise, aGlobal, aBlob, aCropRect) + { + } + + bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + ErrorResult error; + DoCreateImageBitmapFromBlob(error); + return !(error.Failed()); + } + +private: + already_AddRefed CreateImageBitmap(ErrorResult& aRv) override + { + nsRefPtr data; + + nsRefPtr task = + new DecodeBlobInMainThreadSyncTask(mWorkerPrivate, *mBlob, mCropRect, + aRv, getter_AddRefs(data)); + task->Dispatch(mWorkerPrivate->GetJSContext()); // This is a synchronous call. + + if (NS_WARN_IF(aRv.Failed())) { + mPromise->MaybeReject(aRv); + return nullptr; + } + + // Create ImageBitmap object. + nsRefPtr imageBitmap = new ImageBitmap(mGlobalObject, data); + return imageBitmap.forget(); + } + +}; + +static void +AsyncCreateImageBitmapFromBlob(Promise* aPromise, nsIGlobalObject* aGlobal, + Blob& aBlob, const Maybe& aCropRect) +{ + if (NS_IsMainThread()) { + nsCOMPtr task = + new CreateImageBitmapFromBlobTask(aPromise, aGlobal, aBlob, aCropRect); + NS_DispatchToCurrentThread(task); // Actually, to the main-thread. + } else { + nsRefPtr task = + new CreateImageBitmapFromBlobWorkerTask(aPromise, aGlobal, aBlob, aCropRect); + task->Dispatch(GetCurrentThreadWorkerPrivate()->GetJSContext()); // Actually, to the current worker-thread. + } +} + +/* static */ already_AddRefed +ImageBitmap::Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc, + const Maybe& aCropRect, ErrorResult& aRv) +{ + MOZ_ASSERT(aGlobal); + + nsRefPtr promise = Promise::Create(aGlobal, aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + if (aCropRect.isSome() && (aCropRect->Width() == 0 || aCropRect->Height() == 0)) { + aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); + return promise.forget(); + } + + nsRefPtr imageBitmap; + + if (aSrc.IsHTMLImageElement()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from HTMLImageElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLImageElement(), aCropRect, aRv); + } else if (aSrc.IsHTMLVideoElement()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from HTMLVideoElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLVideoElement(), aCropRect, aRv); + } else if (aSrc.IsHTMLCanvasElement()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from HTMLCanvasElement off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsHTMLCanvasElement(), aCropRect, aRv); + } else if (aSrc.IsImageData()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageData(), aCropRect, aRv); + } else if (aSrc.IsCanvasRenderingContext2D()) { + MOZ_ASSERT(NS_IsMainThread(), + "Creating ImageBitmap from CanvasRenderingContext2D off the main thread."); + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsCanvasRenderingContext2D(), aCropRect, aRv); + } else if (aSrc.IsImageBitmap()) { + imageBitmap = CreateInternal(aGlobal, aSrc.GetAsImageBitmap(), aCropRect, aRv); + } else if (aSrc.IsBlob()) { + AsyncCreateImageBitmapFromBlob(promise, aGlobal, aSrc.GetAsBlob(), aCropRect); + return promise.forget(); + } else { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + } + + if (!aRv.Failed()) { + AsyncFulfillImageBitmapPromise(promise, imageBitmap); + } + + return promise.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/canvas/ImageBitmap.h b/dom/canvas/ImageBitmap.h new file mode 100644 index 0000000000..cdf311a480 --- /dev/null +++ b/dom/canvas/ImageBitmap.h @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ImageBitmap_h +#define mozilla_dom_ImageBitmap_h + +#include "mozilla/Attributes.h" +#include "mozilla/dom/ImageBitmapSource.h" +#include "mozilla/dom/TypedArray.h" +#include "mozilla/gfx/Rect.h" +#include "mozilla/Maybe.h" +#include "nsCycleCollectionParticipant.h" + +struct JSContext; + +namespace mozilla { + +class ErrorResult; + +namespace gfx { +class SourceSurface; +} + +namespace layers { +class Image; +} + +namespace dom { + +class CanvasRenderingContext2D; +class File; +class HTMLCanvasElement; +class HTMLImageElement; +class HTMLVideoElement; +class ImageData; +class Promise; +class CreateImageBitmapFromBlob; +class CreateImageBitmapFromBlobTask; +class CreateImageBitmapFromBlobWorkerTask; + +/* + * ImageBitmap is an opaque handler to several kinds of image-like objects from + * HTMLImageElement, HTMLVideoElement, HTMLCanvasElement, ImageData to + * CanvasRenderingContext2D and Image Blob. + * + * An ImageBitmap could be painted to a canvas element. + * + * Generally, an ImageBitmap only keeps a reference to its source object's + * buffer, but if the source object is an ImageData, an Blob or a + * HTMLCanvasElement with WebGL rendering context, the ImageBitmap copy the + * source object's buffer. + */ +class ImageBitmap final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ImageBitmap) + + nsCOMPtr GetParentObject() const { return mParent; } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + uint32_t Width() const + { + return mPictureRect.Width(); + } + + uint32_t Height() const + { + return mPictureRect.Height(); + } + + /* + * The PrepareForDrawTarget() might return null if the mPictureRect does not + * intersect with the size of mData. + */ + already_AddRefed + PrepareForDrawTarget(gfx::DrawTarget* aTarget); + + static already_AddRefed + Create(nsIGlobalObject* aGlobal, const ImageBitmapSource& aSrc, + const Maybe& aCropRect, ErrorResult& aRv); + + friend CreateImageBitmapFromBlob; + friend CreateImageBitmapFromBlobTask; + friend CreateImageBitmapFromBlobWorkerTask; + +protected: + + ImageBitmap(nsIGlobalObject* aGlobal, layers::Image* aData); + + virtual ~ImageBitmap(); + + void SetPictureRect(const gfx::IntRect& aRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, HTMLImageElement& aImageEl, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, HTMLVideoElement& aVideoEl, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvasEl, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, CanvasRenderingContext2D& aCanvasCtx, + const Maybe& aCropRect, ErrorResult& aRv); + + static already_AddRefed + CreateInternal(nsIGlobalObject* aGlobal, ImageBitmap& aImageBitmap, + const Maybe& aCropRect, ErrorResult& aRv); + + nsCOMPtr mParent; + + /* + * The mData is the data buffer of an ImageBitmap, so the mData must not be + * null. + * + * The mSurface is a cache for drawing the ImageBitmap onto a + * HTMLCanvasElement. The mSurface is null while the ImageBitmap is created + * and then will be initialized while the PrepareForDrawTarget() method is + * called first time. + * + * The mSurface might just be a reference to the same data buffer of the mData + * if the are of mPictureRect is just the same as the mData's size. Or, it is + * a independent data buffer which is copied and cropped form the mData's data + * buffer. + */ + nsRefPtr mData; + RefPtr mSurface; + + /* + * The mPictureRect is the size of the source image in default, however, if + * users specify the cropping area while creating an ImageBitmap, then this + * mPictureRect is the cropping area. + * + * Note that if the CreateInternal() copies and crops data from the source + * image, then this mPictureRect is just the size of the final mData. + * + * The mPictureRect will be used at PrepareForDrawTarget() while user is going + * to draw this ImageBitmap into a HTMLCanvasElement. + */ + gfx::IntRect mPictureRect; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ImageBitmap_h + + diff --git a/dom/canvas/ImageBitmapSource.h b/dom/canvas/ImageBitmapSource.h new file mode 100644 index 0000000000..d81990715e --- /dev/null +++ b/dom/canvas/ImageBitmapSource.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_ImageBitmapSource_h +#define mozilla_dom_ImageBitmapSource_h + +namespace mozilla { +namespace dom { + +// So we don't have to forward declare this elsewhere. +class HTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrBlobOrImageDataOrCanvasRenderingContext2DOrImageBitmap; +typedef HTMLImageElementOrHTMLVideoElementOrHTMLCanvasElementOrBlobOrImageDataOrCanvasRenderingContext2DOrImageBitmap + ImageBitmapSource; + +} +} + +#endif diff --git a/dom/canvas/moz.build b/dom/canvas/moz.build index 628aaed3c8..a3d422c9a1 100644 --- a/dom/canvas/moz.build +++ b/dom/canvas/moz.build @@ -31,6 +31,8 @@ EXPORTS.mozilla.dom += [ 'CanvasPattern.h', 'CanvasRenderingContext2D.h', 'CanvasUtils.h', + 'ImageBitmap.h', + 'ImageBitmapSource.h', 'ImageData.h', 'TextMetrics.h', 'WebGLVertexArrayObject.h', @@ -46,6 +48,7 @@ UNIFIED_SOURCES += [ 'CanvasUtils.cpp', 'DocumentRendererChild.cpp', 'DocumentRendererParent.cpp', + 'ImageBitmap.cpp', 'ImageData.cpp', ] @@ -144,6 +147,7 @@ include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ + '../workers', '/dom/base', '/dom/html', '/dom/svg', @@ -154,6 +158,7 @@ LOCAL_INCLUDES += [ '/layout/generic', '/layout/style', '/layout/xul', + '/media/libyuv/include', ] CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] diff --git a/dom/canvas/test/image_error-early.png b/dom/canvas/test/image_error-early.png new file mode 100644 index 0000000000..5df7507e2d --- /dev/null +++ b/dom/canvas/test/image_error-early.png @@ -0,0 +1 @@ +ERROR diff --git a/dom/canvas/test/imagebitmap_on_worker.js b/dom/canvas/test/imagebitmap_on_worker.js new file mode 100644 index 0000000000..96a01e459f --- /dev/null +++ b/dom/canvas/test/imagebitmap_on_worker.js @@ -0,0 +1,81 @@ +function ok(expect, msg) { + postMessage({type: "status", status: !!expect, msg: msg}); +} + +function doneTask() { + postMessage({type: "doneTask"}); +} + +function promiseThrows(p, name) { + var didThrow; + return p.then(function() { didThrow = false; }, + function() { didThrow = true; }) + .then(function() { ok(didThrow, "[TestException] " + name); }); +} + +onmessage = function(event) { + if (event.data.type == "testImageData") { + var width = event.data.width; + var height = event.data.height; + var imageData = event.data.source; + ok(imageData, "[CreateFromImageData] An ImageData is passed into worker.") + ok(imageData.width == width, "[CreateFromImageData] Passed ImageData has right width = " + width); + ok(imageData.height == height, "[CreateFromImageData] Passed ImageData has right height = " + height); + + var promise = createImageBitmap(imageData); + promise.then(function(bitmap) { + ok(bitmap, "[CreateFromImageData] ImageBitmap is created successfully."); + ok(bitmap.width == width, "[CreateFromImageData] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width); + ok(bitmap.height == height, "[CreateFromImageData] ImageBitmap.height = " + bitmap.height + ", expected height = " + height); + + doneTask(); + }); + } else if (event.data.type == "testBlob") { + var width = event.data.width; + var height = event.data.height; + var blob = event.data.source; + ok(blob, "[CreateFromBlob] A Blob object is passed into worker."); + + var promise = createImageBitmap(blob); + promise.then(function(bitmap) { + ok(bitmap, "[CreateFromBlob] ImageBitmap is created successfully."); + ok(bitmap.width == width, "[CreateFromBlob] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width); + ok(bitmap.height == height, "[CreateFromBlob] ImageBitmap.height = " + bitmap.height + ", expected height = " + height); + + doneTask(); + }); + } else if (event.data.type == "testImageBitmap") { + var width = event.data.width; + var height = event.data.height; + var source = event.data.source; + ok(source, "[CreateFromImageBitmap] A soruce object is passed into worker."); + + var promise = createImageBitmap(source); + promise.then(function(bitmap) { + ok(bitmap, "[CreateFromImageBitmap] ImageBitmap is created successfully."); + ok(bitmap.width == width, "[CreateFromImageBitmap] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width); + ok(bitmap.height == height, "[CreateFromImageBitmap] ImageBitmap.height = " + bitmap.height + ", expected height = " + height); + + var promise2 = createImageBitmap(bitmap); + promise2.then(function(bitmap2) { + ok(bitmap2, "[CreateFromImageBitmap] 2nd ImageBitmap is created successfully."); + ok(bitmap.width == width, "[CreateFromImageBitmap] ImageBitmap.width = " + bitmap.width + ", expected witdth = " + width); + ok(bitmap.height == height, "[CreateFromImageBitmap] ImageBitmap.height = " + bitmap.height + ", expected height = " + height); + + doneTask(); + }); + }); + } else if (event.data.type == "testException") { + var source = event.data.source; + if (event.data.sx) { + var sx = event.data.sx; + var sy = event.data.sy; + var sw = event.data.sw; + var sh = event.data.sh; + promiseThrows(createImageBitmap(source, sx, sy, sw, sh), event.data.msg); + } else { + promiseThrows(createImageBitmap(source), event.data.msg); + } + doneTask(); + } +}; \ No newline at end of file diff --git a/dom/canvas/test/imagebitmap_structuredclone.js b/dom/canvas/test/imagebitmap_structuredclone.js new file mode 100644 index 0000000000..80640e461c --- /dev/null +++ b/dom/canvas/test/imagebitmap_structuredclone.js @@ -0,0 +1,30 @@ +function ok(expect, msg) { + postMessage({"type": "status", status: !!expect, msg: msg}); +} + +onmessage = function(event) { + ok(!!event.data.bitmap1, "Get the 1st ImageBitmap from the main script."); + ok(!!event.data.bitmap2, "Get the 2st ImageBitmap from the main script."); + + // send the first original ImageBitmap back to the main-thread + postMessage({"type":"bitmap1", + "bitmap":event.data.bitmap1}); + + // create a new ImageBitmap from the 2nd original ImageBitmap + // and then send the newly created ImageBitmap back to the main-thread + var promise = createImageBitmap(event.data.bitmap2); + promise.then( + function(bitmap) { + ok(true, "Successfully create a new ImageBitmap from the 2nd original bitmap in worker."); + + // send the newly created ImageBitmap back to the main-thread + postMessage({"type":"bitmap2", "bitmap":bitmap}); + + // finish the test + postMessage({"type": "finish"}); + }, + function() { + ok(false, "Cannot create a new bitmap from the original bitmap in worker."); + } + ); +} diff --git a/dom/canvas/test/imagebitmap_structuredclone_iframe.html b/dom/canvas/test/imagebitmap_structuredclone_iframe.html new file mode 100644 index 0000000000..b14b403123 --- /dev/null +++ b/dom/canvas/test/imagebitmap_structuredclone_iframe.html @@ -0,0 +1,38 @@ + + + + + diff --git a/dom/canvas/test/mochitest.ini b/dom/canvas/test/mochitest.ini index 21585c11c8..48b4b743db 100644 --- a/dom/canvas/test/mochitest.ini +++ b/dom/canvas/test/mochitest.ini @@ -6,6 +6,7 @@ support-files = image_anim-gr.png image_anim-poster-gr.png image_broken.png + image_error-early.png image_ggrr-256x256.png image_green-16x16.png image_green-1x1.png @@ -23,6 +24,9 @@ support-files = image_transparent50.png image_yellow.png image_yellow75.png + imagebitmap_on_worker.js + imagebitmap_structuredclone.js + imagebitmap_structuredclone_iframe.html [test_2d.clearRect.image.offscreen.html] [test_2d.clip.winding.html] @@ -234,6 +238,11 @@ support-files = captureStream_common.js [test_drawWindow.html] support-files = file_drawWindow_source.html file_drawWindow_common.js skip-if = buildapp == 'mulet' || (buildapp == 'b2g' && toolkit != 'gonk') +[test_imagebitmap.html] +[test_imagebitmap_on_worker.html] +[test_imagebitmap_structuredclone.html] +[test_imagebitmap_structuredclone_iframe.html] +[test_imagebitmap_structuredclone_window.html] [test_ImageData_ctor.html] [test_isPointInStroke.html] [test_mozDashOffset.html] diff --git a/dom/canvas/test/test_imagebitmap.html b/dom/canvas/test/test_imagebitmap.html new file mode 100644 index 0000000000..df9a2ec4af --- /dev/null +++ b/dom/canvas/test/test_imagebitmap.html @@ -0,0 +1,342 @@ + +Test ImageBitmap + + + + + + + + + + + + + diff --git a/dom/canvas/test/test_imagebitmap_on_worker.html b/dom/canvas/test/test_imagebitmap_on_worker.html new file mode 100644 index 0000000000..3dbe0a638e --- /dev/null +++ b/dom/canvas/test/test_imagebitmap_on_worker.html @@ -0,0 +1,139 @@ + +Test ImageBitmap on Worker + + + + + + diff --git a/dom/canvas/test/test_imagebitmap_structuredclone.html b/dom/canvas/test/test_imagebitmap_structuredclone.html new file mode 100644 index 0000000000..faf178c3c6 --- /dev/null +++ b/dom/canvas/test/test_imagebitmap_structuredclone.html @@ -0,0 +1,105 @@ + +Test ImageBitmap : Structured Clone + + + + + + diff --git a/dom/canvas/test/test_imagebitmap_structuredclone_iframe.html b/dom/canvas/test/test_imagebitmap_structuredclone_iframe.html new file mode 100644 index 0000000000..02f80b05c6 --- /dev/null +++ b/dom/canvas/test/test_imagebitmap_structuredclone_iframe.html @@ -0,0 +1,112 @@ + +Test ImageBitmap : StructuredClone between main window and iframe + + + + +
+ + diff --git a/dom/canvas/test/test_imagebitmap_structuredclone_window.html b/dom/canvas/test/test_imagebitmap_structuredclone_window.html new file mode 100644 index 0000000000..5aa5bfd378 --- /dev/null +++ b/dom/canvas/test/test_imagebitmap_structuredclone_window.html @@ -0,0 +1,95 @@ + +Test ImageBitmap : StructuredClone main window to main window + + + + + + diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index 5a3751da3b..2186ece72b 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -45,13 +45,6 @@ using namespace mozilla::gfx; NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas) -namespace { - -typedef mozilla::dom::HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement -HTMLImageOrCanvasOrVideoElement; - -} // namespace - namespace mozilla { namespace dom { @@ -283,10 +276,10 @@ HTMLCanvasElement::CopyInnerTo(Element* aDest) nsRefPtr context2d = static_cast(cxt.get()); if (context2d && !mPrintCallback) { - HTMLImageOrCanvasOrVideoElement element; - element.SetAsHTMLCanvasElement() = this; + CanvasImageSource source; + source.SetAsHTMLCanvasElement() = this; ErrorResult err; - context2d->DrawImage(element, + context2d->DrawImage(source, 0.0, 0.0, err); rv = err.StealNSResult(); } @@ -750,9 +743,12 @@ GetCanvasContextType(const nsAString& str, CanvasContextType* const out_type) static already_AddRefed CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas) { + MOZ_ASSERT(contextType != CanvasContextType::NoContext); nsRefPtr ret; switch (contextType) { + case CanvasContextType::NoContext: + break; case CanvasContextType::Canvas2D: Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1); ret = new CanvasRenderingContext2D(); diff --git a/dom/html/HTMLCanvasElement.h b/dom/html/HTMLCanvasElement.h index dd0073c3a2..b6387c8763 100644 --- a/dom/html/HTMLCanvasElement.h +++ b/dom/html/HTMLCanvasElement.h @@ -36,6 +36,7 @@ class HTMLCanvasPrintState; class PrintCallback; enum class CanvasContextType : uint8_t { + NoContext, Canvas2D, WebGL1, WebGL2 @@ -262,6 +263,10 @@ public: void ResetPrintCallback(); HTMLCanvasElement* GetOriginalCanvas(); + + CanvasContextType GetCurrentContextType() { + return mCurrentContextType; + } }; class HTMLCanvasPrintState final : public nsWrapperCache diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp index c922bbed9b..c969b2ee1f 100644 --- a/dom/messagechannel/MessagePort.cpp +++ b/dom/messagechannel/MessagePort.cpp @@ -116,25 +116,17 @@ public: JSContext* cx = jsapi.cx(); - nsTArray> ports; nsCOMPtr window = do_QueryInterface(mPort->GetParentObject()); + ErrorResult rv; JS::Rooted value(cx); - if (!mData->mData.IsEmpty()) { - bool ok = ReadStructuredCloneWithTransfer(cx, mData->mData, - mData->mClosure, - &value, window, ports); - FreeStructuredClone(mData->mData, mData->mClosure); - if (!ok) { - return NS_ERROR_FAILURE; - } + mData->Read(window, cx, &value, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); } - // The data should be already be cleaned. - MOZ_ASSERT(!mData->mData.Length()); - // Create the event nsCOMPtr eventTarget = do_QueryInterface(mPort->GetOwner()); @@ -148,14 +140,9 @@ public: event->SetTrusted(true); event->SetSource(mPort); - nsTArray> array; - array.SetCapacity(ports.Length()); - for (uint32_t i = 0; i < ports.Length(); ++i) { - array.AppendElement(ports[i]); - } - nsRefPtr portList = - new MessagePortList(static_cast(event.get()), array); + new MessagePortList(static_cast(event.get()), + mData->GetTransferredPorts()); event->SetPorts(portList); bool dummy; @@ -449,9 +436,8 @@ MessagePort::PostMessage(JSContext* aCx, JS::Handle aMessage, nsRefPtr data = new SharedMessagePortMessage(); - if (!WriteStructuredCloneWithTransfer(aCx, aMessage, transferable, - data->mData, data->mClosure)) { - aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + data->Write(aCx, aMessage, transferable, aRv); + if (NS_WARN_IF(aRv.Failed())) { return; } diff --git a/dom/messagechannel/MessagePortList.h b/dom/messagechannel/MessagePortList.h index 23cdfdfe22..7bea0ab549 100644 --- a/dom/messagechannel/MessagePortList.h +++ b/dom/messagechannel/MessagePortList.h @@ -28,7 +28,8 @@ public: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MessagePortList) public: - MessagePortList(nsISupports* aOwner, nsTArray>& aPorts) + MessagePortList(nsISupports* aOwner, + const nsTArray>& aPorts) : mOwner(aOwner) , mPorts(aPorts) { diff --git a/dom/messagechannel/MessagePortUtils.cpp b/dom/messagechannel/MessagePortUtils.cpp deleted file mode 100644 index ccd3b13043..0000000000 --- a/dom/messagechannel/MessagePortUtils.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* 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 "MessagePortUtils.h" -#include "MessagePort.h" -#include "mozilla/dom/BlobBinding.h" -#include "mozilla/dom/File.h" -#include "mozilla/dom/MessagePortBinding.h" -#include "mozilla/dom/StructuredCloneTags.h" - -namespace mozilla { -namespace dom { -namespace messageport { - -namespace { - -struct MOZ_STACK_CLASS StructuredCloneClosureInternal -{ - StructuredCloneClosureInternal( - StructuredCloneClosure& aClosure, nsPIDOMWindow* aWindow) - : mClosure(aClosure) - , mWindow(aWindow) - { } - - StructuredCloneClosure& mClosure; - nsPIDOMWindow* mWindow; - nsTArray> mMessagePorts; - nsTArray> mTransferredPorts; -}; - -struct MOZ_STACK_CLASS StructuredCloneClosureInternalReadOnly -{ - StructuredCloneClosureInternalReadOnly( - const StructuredCloneClosure& aClosure, nsPIDOMWindow* aWindow) - : mClosure(aClosure) - , mWindow(aWindow) - { } - - const StructuredCloneClosure& mClosure; - nsPIDOMWindow* mWindow; - nsTArray> mMessagePorts; - nsTArray> mTransferredPorts; -}; - -void -Error(JSContext* aCx, uint32_t aErrorId) -{ - if (NS_IsMainThread()) { - NS_DOMStructuredCloneError(aCx, aErrorId); - } else { - Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR); - } -} - -JSObject* -Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag, - uint32_t aData, void* aClosure) -{ - MOZ_ASSERT(aClosure); - - auto* closure = static_cast(aClosure); - - if (aTag == SCTAG_DOM_BLOB) { - // nsRefPtr needs to go out of scope before toObjectOrNull() is - // called because the static analysis thinks dereferencing XPCOM objects - // can GC (because in some cases it can!), and a return statement with a - // JSObject* type means that JSObject* is on the stack as a raw pointer - // while destructors are running. - JS::Rooted val(aCx); - { - MOZ_ASSERT(aData < closure->mClosure.mBlobImpls.Length()); - nsRefPtr blobImpl = closure->mClosure.mBlobImpls[aData]; - -#ifdef DEBUG - { - // Blob should not be mutable. - bool isMutable; - MOZ_ASSERT(NS_SUCCEEDED(blobImpl->GetMutable(&isMutable))); - MOZ_ASSERT(!isMutable); - } -#endif - - // Let's create a new blob with the correct parent. - nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); - MOZ_ASSERT(global); - - nsRefPtr newBlob = Blob::Create(global, blobImpl); - if (!ToJSValue(aCx, newBlob, &val)) { - return nullptr; - } - } - - return &val.toObject(); - } - - return NS_DOMReadStructuredClone(aCx, aReader, aTag, aData, nullptr); -} - -bool -Write(JSContext* aCx, JSStructuredCloneWriter* aWriter, - JS::Handle aObj, void* aClosure) -{ - MOZ_ASSERT(aClosure); - - auto* closure = static_cast(aClosure); - - // See if the wrapped native is a File/Blob. - { - Blob* blob = nullptr; - if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) && - NS_SUCCEEDED(blob->SetMutable(false)) && - JS_WriteUint32Pair(aWriter, SCTAG_DOM_BLOB, - closure->mClosure.mBlobImpls.Length())) { - closure->mClosure.mBlobImpls.AppendElement(blob->Impl()); - return true; - } - } - - return NS_DOMWriteStructuredClone(aCx, aWriter, aObj, nullptr); -} - -bool -ReadTransfer(JSContext* aCx, JSStructuredCloneReader* aReader, - uint32_t aTag, void* aContent, uint64_t aExtraData, - void* aClosure, JS::MutableHandle aReturnObject) -{ - MOZ_ASSERT(NS_IsMainThread()); - MOZ_ASSERT(aClosure); - - auto* closure = static_cast(aClosure); - - if (aTag != SCTAG_DOM_MAP_MESSAGEPORT) { - return false; - } - - MOZ_ASSERT(aContent == 0); - MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length()); - - ErrorResult rv; - nsRefPtr port = - MessagePort::Create(closure->mWindow, - closure->mClosure.mMessagePortIdentifiers[aExtraData], - rv); - if (NS_WARN_IF(rv.Failed())) { - return false; - } - - closure->mMessagePorts.AppendElement(port); - - JS::Rooted value(aCx); - if (!GetOrCreateDOMReflector(aCx, port, &value)) { - JS_ClearPendingException(aCx); - return false; - } - - aReturnObject.set(&value.toObject()); - return true; -} - -bool -WriteTransfer(JSContext* aCx, JS::Handle aObj, void* aClosure, - uint32_t* aTag, JS::TransferableOwnership* aOwnership, - void** aContent, uint64_t* aExtraData) -{ - MOZ_ASSERT(aClosure); - - auto* closure = static_cast(aClosure); - - MessagePortBase* port = nullptr; - nsresult rv = UNWRAP_OBJECT(MessagePort, aObj, port); - if (NS_FAILED(rv)) { - return false; - } - - if (closure->mTransferredPorts.Contains(port)) { - // No duplicates. - return false; - } - - MessagePortIdentifier identifier; - if (!port->CloneAndDisentangle(identifier)) { - return false; - } - - closure->mClosure.mMessagePortIdentifiers.AppendElement(identifier); - closure->mTransferredPorts.AppendElement(port); - - *aTag = SCTAG_DOM_MAP_MESSAGEPORT; - *aOwnership = JS::SCTAG_TMO_CUSTOM; - *aContent = nullptr; - *aExtraData = closure->mClosure.mMessagePortIdentifiers.Length() - 1; - - return true; -} - -void -FreeTransfer(uint32_t aTag, JS::TransferableOwnership aOwnership, - void* aContent, uint64_t aExtraData, void* aClosure) -{ - MOZ_ASSERT(aClosure); - auto* closure = static_cast(aClosure); - - if (aTag == SCTAG_DOM_MAP_MESSAGEPORT) { - MOZ_ASSERT(!aContent); - MOZ_ASSERT(aExtraData < closure->mClosure.mMessagePortIdentifiers.Length()); - MessagePort::ForceClose(closure->mClosure.mMessagePortIdentifiers[(uint32_t)aExtraData]); - } -} - -const JSStructuredCloneCallbacks gCallbacks = { - Read, - Write, - Error, - ReadTransfer, - WriteTransfer, - FreeTransfer, -}; - -} // anonymous namespace - -bool -ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray& aData, - const StructuredCloneClosure& aClosure, - JS::MutableHandle aClone, - nsPIDOMWindow* aParentWindow, - nsTArray>& aMessagePorts) -{ - auto* data = reinterpret_cast(aData.Elements()); - size_t dataLen = aData.Length(); - MOZ_ASSERT(!(dataLen % sizeof(*data))); - - StructuredCloneClosureInternalReadOnly internalClosure(aClosure, - aParentWindow); - - bool rv = JS_ReadStructuredClone(aCx, data, dataLen, - JS_STRUCTURED_CLONE_VERSION, aClone, - &gCallbacks, &internalClosure); - if (rv) { - aMessagePorts.SwapElements(internalClosure.mMessagePorts); - } - - return rv; -} - -bool -WriteStructuredCloneWithTransfer(JSContext* aCx, JS::Handle aSource, - JS::Handle aTransferable, - nsTArray& aData, - StructuredCloneClosure& aClosure) -{ - StructuredCloneClosureInternal internalClosure(aClosure, nullptr); - JSAutoStructuredCloneBuffer buffer(&gCallbacks, &internalClosure); - - if (!buffer.write(aCx, aSource, aTransferable, &gCallbacks, - &internalClosure)) { - return false; - } - - FallibleTArray cloneData; - if (NS_WARN_IF(!cloneData.SetLength(buffer.nbytes(), mozilla::fallible))) { - return false; - } - - uint64_t* data; - size_t size; - buffer.steal(&data, &size); - - memcpy(cloneData.Elements(), data, size); - js_free(data); - - MOZ_ASSERT(aData.IsEmpty()); - aData.SwapElements(cloneData); - return true; -} - -void -FreeStructuredClone(nsTArray& aData, StructuredCloneClosure& aClosure) -{ - auto* data = reinterpret_cast(aData.Elements()); - size_t dataLen = aData.Length(); - MOZ_ASSERT(!(dataLen % sizeof(*data))); - - JS_ClearStructuredClone(data, dataLen, &gCallbacks, &aClosure, false); - aData.Clear(); -} - -} // messageport namespace -} // dom namespace -} // mozilla namespace diff --git a/dom/messagechannel/MessagePortUtils.h b/dom/messagechannel/MessagePortUtils.h deleted file mode 100644 index 9c9442700e..0000000000 --- a/dom/messagechannel/MessagePortUtils.h +++ /dev/null @@ -1,55 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_MessagePortUtils_h -#define mozilla_dom_MessagePortUtils_h - -#include "MessagePort.h" -#include "mozilla/dom/File.h" -#include "mozilla/dom/PMessagePort.h" - -class nsPIDOMWindow; - -namespace mozilla { -namespace dom { -namespace messageport { - -struct -StructuredCloneClosure -{ - nsTArray> mBlobImpls; - nsTArray mMessagePortIdentifiers; -}; - -struct -StructuredCloneData -{ - StructuredCloneData() : mData(nullptr), mDataLength(0) {} - uint64_t* mData; - size_t mDataLength; - StructuredCloneClosure mClosure; -}; - -bool -ReadStructuredCloneWithTransfer(JSContext* aCx, nsTArray& aData, - const StructuredCloneClosure& aClosure, - JS::MutableHandle aClone, - nsPIDOMWindow* aParentWindow, - nsTArray>& aMessagePorts); - -bool -WriteStructuredCloneWithTransfer(JSContext* aCx, JS::Handle aSource, - JS::Handle aTransferable, - nsTArray& aData, - StructuredCloneClosure& aClosure); - -void -FreeStructuredClone(nsTArray& aData, - StructuredCloneClosure& aClosure); - -} // messageport namespace -} // dom namespace -} // mozilla namespace - -#endif // mozilla_dom_MessagePortUtils_h diff --git a/dom/messagechannel/SharedMessagePortMessage.cpp b/dom/messagechannel/SharedMessagePortMessage.cpp index bbd8fbdbdd..55ce97e6dc 100644 --- a/dom/messagechannel/SharedMessagePortMessage.cpp +++ b/dom/messagechannel/SharedMessagePortMessage.cpp @@ -20,13 +20,74 @@ using namespace ipc; namespace dom { -SharedMessagePortMessage::~SharedMessagePortMessage() +void +SharedMessagePortMessage::Read(nsISupports* aParent, + JSContext* aCx, + JS::MutableHandle aValue, + ErrorResult& aRv) +{ + if (mData.IsEmpty()) { + return; + } + + auto* data = reinterpret_cast(mData.Elements()); + size_t dataLen = mData.Length(); + MOZ_ASSERT(!(dataLen % sizeof(*data))); + + ReadFromBuffer(aParent, aCx, data, dataLen, aValue, aRv); + NS_WARN_IF(aRv.Failed()); + + Free(); +} + +void +SharedMessagePortMessage::Write(JSContext* aCx, + JS::Handle aValue, + JS::Handle aTransfer, + ErrorResult& aRv) +{ + StructuredCloneHelper::Write(aCx, aValue, aTransfer, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + const nsTArray>& blobImpls = BlobImpls(); + for (uint32_t i = 0, len = blobImpls.Length(); i < len; ++i) { + if (!blobImpls[i]->MayBeClonedToOtherThreads()) { + aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + return; + } + } + + FallibleTArray cloneData; + + MoveBufferDataToArray(cloneData, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + MOZ_ASSERT(mData.IsEmpty()); + mData.SwapElements(cloneData); +} + +void +SharedMessagePortMessage::Free() { if (!mData.IsEmpty()) { - FreeStructuredClone(mData, mClosure); + auto* data = reinterpret_cast(mData.Elements()); + size_t dataLen = mData.Length(); + MOZ_ASSERT(!(dataLen % sizeof(*data))); + + FreeBuffer(data, dataLen); + mData.Clear(); } } +SharedMessagePortMessage::~SharedMessagePortMessage() +{ + Free(); +} + /* static */ void SharedMessagePortMessage::FromSharedToMessagesChild( MessagePortChild* aActor, @@ -44,8 +105,7 @@ SharedMessagePortMessage::FromSharedToMessagesChild( MessagePortMessage* message = aArray.AppendElement(); message->data().SwapElements(data->mData); - const nsTArray>& blobImpls = - data->mClosure.mBlobImpls; + const nsTArray>& blobImpls = data->BlobImpls(); if (!blobImpls.IsEmpty()) { message->blobsChild().SetCapacity(blobImpls.Length()); @@ -57,8 +117,7 @@ SharedMessagePortMessage::FromSharedToMessagesChild( } } - message->transferredPorts().AppendElements( - data->mClosure.mMessagePortIdentifiers); + message->transferredPorts().AppendElements(data->PortIdentifiers()); } } @@ -80,17 +139,16 @@ SharedMessagePortMessage::FromMessagesToSharedChild( const nsTArray& blobs = message.blobsChild(); if (!blobs.IsEmpty()) { - data->mClosure.mBlobImpls.SetCapacity(blobs.Length()); + data->BlobImpls().SetCapacity(blobs.Length()); for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { nsRefPtr impl = static_cast(blobs[i])->GetBlobImpl(); - data->mClosure.mBlobImpls.AppendElement(impl); + data->BlobImpls().AppendElement(impl); } } - data->mClosure.mMessagePortIdentifiers.AppendElements( - message.transferredPorts()); + data->PortIdentifiers().AppendElements(message.transferredPorts()); if (!aData.AppendElement(data, mozilla::fallible)) { return false; @@ -119,7 +177,7 @@ SharedMessagePortMessage::FromSharedToMessagesParent( MessagePortMessage* message = aArray.AppendElement(mozilla::fallible); message->data().SwapElements(data->mData); - const nsTArray>& blobImpls = data->mClosure.mBlobImpls; + const nsTArray>& blobImpls = data->BlobImpls(); if (!blobImpls.IsEmpty()) { message->blobsParent().SetCapacity(blobImpls.Length()); @@ -131,8 +189,7 @@ SharedMessagePortMessage::FromSharedToMessagesParent( } } - message->transferredPorts().AppendElements( - data->mClosure.mMessagePortIdentifiers); + message->transferredPorts().AppendElements(data->PortIdentifiers()); } return true; @@ -156,17 +213,16 @@ SharedMessagePortMessage::FromMessagesToSharedParent( const nsTArray& blobs = message.blobsParent(); if (!blobs.IsEmpty()) { - data->mClosure.mBlobImpls.SetCapacity(blobs.Length()); + data->BlobImpls().SetCapacity(blobs.Length()); for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) { nsRefPtr impl = static_cast(blobs[i])->GetBlobImpl(); - data->mClosure.mBlobImpls.AppendElement(impl); + data->BlobImpls().AppendElement(impl); } } - data->mClosure.mMessagePortIdentifiers.AppendElements( - message.transferredPorts()); + data->PortIdentifiers().AppendElements(message.transferredPorts()); if (!aData.AppendElement(data, mozilla::fallible)) { return false; diff --git a/dom/messagechannel/SharedMessagePortMessage.h b/dom/messagechannel/SharedMessagePortMessage.h index c2516874ed..d2dbb800fd 100644 --- a/dom/messagechannel/SharedMessagePortMessage.h +++ b/dom/messagechannel/SharedMessagePortMessage.h @@ -6,7 +6,7 @@ #ifndef mozilla_dom_SharedMessagePortMessage_h #define mozilla_dom_SharedMessagePortMessage_h -#include "MessagePortUtils.h" +#include "mozilla/dom/StructuredCloneHelper.h" namespace mozilla { namespace dom { @@ -15,17 +15,29 @@ class MessagePortChild; class MessagePortMessage; class MessagePortParent; -class SharedMessagePortMessage final +class SharedMessagePortMessage final : public StructuredCloneHelper { public: NS_INLINE_DECL_REFCOUNTING(SharedMessagePortMessage) nsTArray mData; - messageport::StructuredCloneClosure mClosure; SharedMessagePortMessage() + : StructuredCloneHelper(CloningSupported, TransferringSupported) {} + void Read(nsISupports* aParent, + JSContext* aCx, + JS::MutableHandle aValue, + ErrorResult& aRv); + + void Write(JSContext* aCx, + JS::Handle aValue, + JS::Handle aTransfer, + ErrorResult& aRv); + + void Free(); + static void FromSharedToMessagesChild( MessagePortChild* aActor, diff --git a/dom/messagechannel/moz.build b/dom/messagechannel/moz.build index c5850dbe0b..f322130655 100644 --- a/dom/messagechannel/moz.build +++ b/dom/messagechannel/moz.build @@ -21,7 +21,6 @@ UNIFIED_SOURCES += [ 'MessagePortList.cpp', 'MessagePortParent.cpp', 'MessagePortService.cpp', - 'MessagePortUtils.cpp', 'SharedMessagePortMessage.cpp', ] diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index ffc3eb6117..9cec0f90cd 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -679,6 +679,8 @@ var interfaceNamesInGlobalScope = "IDBVersionChangeEvent", // IMPORTANT: Do not change this list without review from a DOM peer! "Image", +// IMPORTANT: Do not change this list without review from a DOM peer! + "ImageBitmap", // IMPORTANT: Do not change this list without review from a DOM peer! {name: "ImageCapture", disabled: true}, // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/CanvasRenderingContext2D.webidl b/dom/webidl/CanvasRenderingContext2D.webidl index 781124a432..8b64a7e87a 100644 --- a/dom/webidl/CanvasRenderingContext2D.webidl +++ b/dom/webidl/CanvasRenderingContext2D.webidl @@ -25,6 +25,11 @@ dictionary HitRegionOptions { Element? control = null; }; +typedef (HTMLImageElement or + HTMLCanvasElement or + HTMLVideoElement or + ImageBitmap) CanvasImageSource; + interface CanvasRenderingContext2D { // back-reference to the canvas. Might be null if we're not @@ -63,7 +68,7 @@ interface CanvasRenderingContext2D { [NewObject, Throws] CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); [NewObject, Throws] - CanvasPattern createPattern((HTMLImageElement or HTMLCanvasElement or HTMLVideoElement) image, [TreatNullAs=EmptyString] DOMString repetition); + CanvasPattern createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition); // shadows [LenientFloat] @@ -115,12 +120,13 @@ interface CanvasRenderingContext2D { // drawing images // NOT IMPLEMENTED attribute boolean imageSmoothingEnabled; // (default true) + [Throws, LenientFloat] - void drawImage((HTMLImageElement or HTMLCanvasElement or HTMLVideoElement) image, double dx, double dy); + void drawImage(CanvasImageSource image, double dx, double dy); [Throws, LenientFloat] - void drawImage((HTMLImageElement or HTMLCanvasElement or HTMLVideoElement) image, double dx, double dy, double dw, double dh); + void drawImage(CanvasImageSource image, double dx, double dy, double dw, double dh); [Throws, LenientFloat] - void drawImage((HTMLImageElement or HTMLCanvasElement or HTMLVideoElement) image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh); + void drawImage(CanvasImageSource image, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh); // hit regions [Pref="canvas.hitregions.enabled", Throws] void addHitRegion(optional HitRegionOptions options); diff --git a/dom/webidl/ImageBitmap.webidl b/dom/webidl/ImageBitmap.webidl new file mode 100644 index 0000000000..a257d63ff3 --- /dev/null +++ b/dom/webidl/ImageBitmap.webidl @@ -0,0 +1,32 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. + * + * The origin of this IDL file is + * https://html.spec.whatwg.org/multipage/webappapis.html#images + */ + +typedef (HTMLImageElement or + HTMLVideoElement or + HTMLCanvasElement or + Blob or + ImageData or + CanvasRenderingContext2D or + ImageBitmap) ImageBitmapSource; + +[Exposed=(Window,Worker)] +interface ImageBitmap { + [Constant] + readonly attribute unsigned long width; + [Constant] + readonly attribute unsigned long height; +}; + +[NoInterfaceObject, Exposed=(Window,Worker)] +interface ImageBitmapFactories { + [Throws] + Promise createImageBitmap(ImageBitmapSource aImage); + [Throws] + Promise createImageBitmap(ImageBitmapSource aImage, long aSx, long aSy, long aSw, long aSh); +}; diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl index 813be98f14..777d5c29f6 100644 --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -485,3 +485,4 @@ interface ChromeWindow { Window implements ChromeWindow; Window implements GlobalFetch; +Window implements ImageBitmapFactories; diff --git a/dom/webidl/WorkerGlobalScope.webidl b/dom/webidl/WorkerGlobalScope.webidl index b7f397154f..f56eb40802 100644 --- a/dom/webidl/WorkerGlobalScope.webidl +++ b/dom/webidl/WorkerGlobalScope.webidl @@ -48,6 +48,7 @@ WorkerGlobalScope implements WindowTimers; WorkerGlobalScope implements WindowBase64; WorkerGlobalScope implements GlobalFetch; WorkerGlobalScope implements IDBEnvironment; +WorkerGlobalScope implements ImageBitmapFactories; // Not implemented yet: bug 1072107. // WorkerGlobalScope implements FontFaceSource; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index ee07a4f22b..4693dded4d 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -260,6 +260,7 @@ WEBIDL_FILES = [ 'IDBRequest.webidl', 'IDBTransaction.webidl', 'IDBVersionChangeEvent.webidl', + 'ImageBitmap.webidl', 'ImageCapture.webidl', 'ImageData.webidl', 'ImageDocument.webidl', diff --git a/dom/workers/DataStore.cpp b/dom/workers/DataStore.cpp index 1b08bcb392..a033497220 100644 --- a/dom/workers/DataStore.cpp +++ b/dom/workers/DataStore.cpp @@ -16,6 +16,7 @@ #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/StructuredCloneHelper.h" #include "mozilla/ErrorResult.h" #include "WorkerPrivate.h" @@ -207,8 +208,8 @@ protected: // A DataStoreRunnable to run DataStore::Put(...) on the main thread. class DataStorePutRunnable final : public DataStoreProxyRunnable + , public StructuredCloneHelper { - JSAutoStructuredCloneBuffer mObjBuffer; const StringOrUnsignedLong& mId; const nsString mRevisionId; ErrorResult& mRv; @@ -223,6 +224,7 @@ public: const nsAString& aRevisionId, ErrorResult& aRv) : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise) + , StructuredCloneHelper(CloningNotSupported, TransferringNotSupported) , mId(aId) , mRevisionId(aRevisionId) , mRv(aRv) @@ -231,10 +233,8 @@ public: aWorkerPrivate->AssertIsOnWorkerThread(); // This needs to be structured cloned while it's still on the worker thread. - if (!mObjBuffer.write(aCx, aObj)) { - JS_ClearPendingException(aCx); - mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); - } + Write(aCx, aObj, mRv); + NS_WARN_IF(mRv.Failed()); } protected: @@ -252,9 +252,8 @@ protected: JSContext* cx = jsapi.cx(); JS::Rooted value(cx); - if (!mObjBuffer.read(cx, &value)) { - JS_ClearPendingException(cx); - mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + Read(mBackingStore->GetParentObject(), cx, &value, mRv); + if (NS_WARN_IF(mRv.Failed())) { return true; } @@ -270,8 +269,8 @@ protected: // A DataStoreRunnable to run DataStore::Add(...) on the main thread. class DataStoreAddRunnable final : public DataStoreProxyRunnable + , public StructuredCloneHelper { - JSAutoStructuredCloneBuffer mObjBuffer; const Optional& mId; const nsString mRevisionId; ErrorResult& mRv; @@ -286,6 +285,7 @@ public: const nsAString& aRevisionId, ErrorResult& aRv) : DataStoreProxyRunnable(aWorkerPrivate, aBackingStore, aWorkerPromise) + , StructuredCloneHelper(CloningNotSupported, TransferringNotSupported) , mId(aId) , mRevisionId(aRevisionId) , mRv(aRv) @@ -294,10 +294,8 @@ public: aWorkerPrivate->AssertIsOnWorkerThread(); // This needs to be structured cloned while it's still on the worker thread. - if (!mObjBuffer.write(aCx, aObj)) { - JS_ClearPendingException(aCx); - mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); - } + Write(aCx, aObj, mRv); + NS_WARN_IF(mRv.Failed()); } protected: @@ -315,9 +313,8 @@ protected: JSContext* cx = jsapi.cx(); JS::Rooted value(cx); - if (!mObjBuffer.read(cx, &value)) { - JS_ClearPendingException(cx); - mRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR); + Read(mBackingStore->GetParentObject(), cx, &value, mRv); + if (NS_WARN_IF(mRv.Failed())) { return true; } diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index 84ac2235d3..1fbb85d48f 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -13,6 +13,7 @@ #include "mozilla/dom/DedicatedWorkerGlobalScopeBinding.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h" @@ -386,6 +387,21 @@ WorkerGlobalScope::GetIndexedDB(ErrorResult& aErrorResult) return indexedDB.forget(); } +already_AddRefed +WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Nothing(), aRv); +} + +already_AddRefed +WorkerGlobalScope::CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv) +{ + return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv); +} + DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate) : WorkerGlobalScope(aWorkerPrivate) { diff --git a/dom/workers/WorkerScope.h b/dom/workers/WorkerScope.h index d00b92c6ab..69c25c32a3 100644 --- a/dom/workers/WorkerScope.h +++ b/dom/workers/WorkerScope.h @@ -12,6 +12,7 @@ #include "mozilla/dom/Headers.h" #include "mozilla/dom/RequestBinding.h" #include "nsWeakReference.h" +#include "mozilla/dom/ImageBitmapSource.h" namespace mozilla { namespace dom { @@ -153,6 +154,14 @@ public: already_AddRefed GetCaches(ErrorResult& aRv); + + already_AddRefed + CreateImageBitmap(const ImageBitmapSource& aImage, ErrorResult& aRv); + + already_AddRefed + CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv); }; class DedicatedWorkerGlobalScope final : public WorkerGlobalScope diff --git a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js index a5ad93dc47..0d1400f31f 100644 --- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js +++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js @@ -143,6 +143,8 @@ var interfaceNamesInGlobalScope = "IDBTransaction", // IMPORTANT: Do not change this list without review from a DOM peer! "IDBVersionChangeEvent", +// IMPORTANT: Do not change this list without review from a DOM peer! + "ImageBitmap", // IMPORTANT: Do not change this list without review from a DOM peer! "ImageData", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/workers/test/test_worker_interfaces.js b/dom/workers/test/test_worker_interfaces.js index 0c1d4d2b55..f96bc6d6ce 100644 --- a/dom/workers/test/test_worker_interfaces.js +++ b/dom/workers/test/test_worker_interfaces.js @@ -135,6 +135,8 @@ var interfaceNamesInGlobalScope = "IDBTransaction", // IMPORTANT: Do not change this list without review from a DOM peer! "IDBVersionChangeEvent", +// IMPORTANT: Do not change this list without review from a DOM peer! + "ImageBitmap", // IMPORTANT: Do not change this list without review from a DOM peer! "ImageData", // IMPORTANT: Do not change this list without review from a DOM peer!