From 50f59ff179b60535ea621c895a5b88c46a213ce3 Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Tue, 18 Oct 2022 17:35:33 +0000 Subject: [PATCH] Bug 1778565 - Editor: Handle nsIFile transferable as blob. r=nika,masayuki Differential Revision: https://phabricator.services.mozilla.com/D155753 --- dom/base/nsContentUtils.cpp | 22 --- editor/libeditor/HTMLEditor.h | 4 +- editor/libeditor/HTMLEditorDataTransfer.cpp | 166 +++++++++--------- .../tests/test_pasteImgFromTransferable.html | 5 + 4 files changed, 86 insertions(+), 111 deletions(-) diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index c52c73693544..6b9249c95f76 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -8086,28 +8086,6 @@ void nsContentUtils::TransferableToIPCTransferable( // Otherwise, handle this as a file. nsCOMPtr blobImpl; if (nsCOMPtr file = do_QueryInterface(data)) { - // FIXME(bug 1778565): Historically, attempting to send a Blob over IPC - // in response to a sync message would lead to a crash, however this - // isn't an issue anymore. Unfortunately, code in HTMLEditor depends on - // the old behaviour, so we need to preserve this oddity, and hope that - // the caller is HTMLEditor and can handle a nsIInputStream. - if (aInSyncMessage) { - nsAutoCString type; - if (IsFileImage(file, type)) { - IPCDataTransferItem* item = - aIPCDataTransfer->items().AppendElement(); - item->flavor() = type; - nsCString data; - SlurpFileToString(file, data); - // FIXME: This can probably be simplified once bug 1783240 lands, as - // `nsCString` will be implicitly serialized in shmem when sent over - // IPDL directly. - item->data() = - IPCDataTransferInputStream(BigBuffer(AsBytes(Span(data)))); - } - continue; - } - if (aParent) { bool isDir = false; if (NS_SUCCEEDED(file->IsDirectory(&isDir)) && isDir) { diff --git a/editor/libeditor/HTMLEditor.h b/editor/libeditor/HTMLEditor.h index d905a253f736..ca84b8284433 100644 --- a/editor/libeditor/HTMLEditor.h +++ b/editor/libeditor/HTMLEditor.h @@ -4261,10 +4261,10 @@ class HTMLEditor final : public EditorBase, * operation is finished. * * @param aBlob The input blob - * @param aWindow The global object under which the read should happen. + * @param aGlobal The global object under which the read should happen. * @param aBlobReader The blob reader object to be notified when finished. */ - static nsresult SlurpBlob(dom::Blob* aBlob, nsPIDOMWindowOuter* aWindow, + static nsresult SlurpBlob(dom::Blob* aBlob, nsIGlobalObject* aGlobal, BlobReader* aBlobReader); /** diff --git a/editor/libeditor/HTMLEditorDataTransfer.cpp b/editor/libeditor/HTMLEditorDataTransfer.cpp index ca68245e5a6c..45e4f23d9aa3 100644 --- a/editor/libeditor/HTMLEditorDataTransfer.cpp +++ b/editor/libeditor/HTMLEditorDataTransfer.cpp @@ -20,13 +20,14 @@ #include "WSRunObject.h" #include "mozilla/dom/Comment.h" -#include "mozilla/dom/Document.h" #include "mozilla/dom/DataTransfer.h" +#include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentFragment.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/DOMStringList.h" #include "mozilla/dom/DOMStringList.h" #include "mozilla/dom/Event.h" +#include "mozilla/dom/FileBlobImpl.h" #include "mozilla/dom/FileReader.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/StaticRange.h" @@ -36,6 +37,7 @@ #include "mozilla/Base64.h" #include "mozilla/BasicEvents.h" #include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" #include "mozilla/OwningNonNull.h" #include "mozilla/Preferences.h" #include "mozilla/Result.h" @@ -54,7 +56,7 @@ #include "nsIDocumentEncoder.h" #include "nsIFile.h" #include "nsIInputStream.h" -#include "nsNameSpaceManager.h" +#include "nsIMIMEService.h" #include "nsINode.h" #include "nsIParserUtils.h" #include "nsIPrincipal.h" @@ -65,6 +67,7 @@ #include "nsIVariant.h" #include "nsLinebreakConverter.h" #include "nsLiteralString.h" +#include "nsNameSpaceManager.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" #include "nsRange.h" @@ -1664,11 +1667,12 @@ HTMLEditor::BlobReader::BlobReader(BlobImpl* aBlob, HTMLEditor* aHTMLEditor, MOZ_ASSERT(mBlob); MOZ_ASSERT(mHTMLEditor); MOZ_ASSERT(mHTMLEditor->IsEditActionDataAvailable()); - MOZ_ASSERT(aPointToInsert.IsSet()); MOZ_ASSERT(mDataTransfer); // Take only offset here since it's our traditional behavior. - AutoEditorDOMPointChildInvalidator storeOnlyWithOffset(mPointToInsert); + if (mPointToInsert.IsSet()) { + AutoEditorDOMPointChildInvalidator storeOnlyWithOffset(mPointToInsert); + } } nsresult HTMLEditor::BlobReader::OnResult(const nsACString& aResult) { @@ -1796,16 +1800,14 @@ NS_IMETHODIMP SlurpBlobEventListener::HandleEvent(Event* aEvent) { } // static -nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsPIDOMWindowOuter* aWindow, +nsresult HTMLEditor::SlurpBlob(Blob* aBlob, nsIGlobalObject* aGlobal, BlobReader* aBlobReader) { MOZ_ASSERT(aBlob); - MOZ_ASSERT(aWindow); + MOZ_ASSERT(aGlobal); MOZ_ASSERT(aBlobReader); - nsCOMPtr inner = aWindow->GetCurrentInnerWindow(); - nsCOMPtr global = do_QueryInterface(inner); RefPtr workerRef; - RefPtr reader = new FileReader(global, workerRef); + RefPtr reader = new FileReader(aGlobal, workerRef); RefPtr eventListener = new SlurpBlobEventListener(aBlobReader); @@ -1835,74 +1837,35 @@ nsresult HTMLEditor::InsertObject( DeleteSelectedContent aDeleteSelectedContent) { MOZ_ASSERT(IsEditActionDataAvailable()); - if (nsCOMPtr blob = do_QueryInterface(aObject)) { - RefPtr br = new BlobReader( - blob, this, aSafeToInsertData, aPointToInsert, aDeleteSelectedContent); - // XXX This is not guaranteed. - MOZ_ASSERT(aPointToInsert.IsSet()); - - RefPtr domBlob = - Blob::Create(aPointToInsert.GetContainer()->GetOwnerGlobal(), blob); - if (!domBlob) { - NS_WARNING("Blob::Create() failed"); - return NS_ERROR_FAILURE; - } - - nsresult rv = SlurpBlob( - domBlob, aPointToInsert.GetContainer()->OwnerDoc()->GetWindow(), br); - NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::::SlurpBlob() failed"); - return rv; - } - + // Check to see if we the file is actually an image. nsAutoCString type(aType); - - // Check to see if we can insert an image file - bool insertAsImage = false; - nsCOMPtr fileObj; if (type.EqualsLiteral(kFileMime)) { - fileObj = do_QueryInterface(aObject); - if (fileObj) { - // Accept any image type fed to us - if (nsContentUtils::IsFileImage(fileObj, type)) { - insertAsImage = true; - } else { - // Reset type. - type.AssignLiteral(kFileMime); + if (nsCOMPtr file = do_QueryInterface(aObject)) { + nsCOMPtr mime = do_GetService("@mozilla.org/mime;1"); + if (NS_WARN_IF(!mime)) { + return NS_ERROR_FAILURE; } - } - } - if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) || - type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime) || - insertAsImage) { - nsCString imageData; - if (insertAsImage) { - nsresult rv = nsContentUtils::SlurpFileToString(fileObj, imageData); + nsresult rv = mime->GetTypeFromFile(file, type); if (NS_FAILED(rv)) { - NS_WARNING("nsContentUtils::SlurpFileToString() failed"); + NS_WARNING("nsIMIMEService::GetTypeFromFile() failed"); return rv; } - } else { - nsCOMPtr imageStream; - if (RefPtr blob = do_QueryObject(aObject)) { - RefPtr file = blob->ToFile(); - if (!file) { - NS_WARNING("No mozilla::dom::File object"); - return NS_ERROR_FAILURE; - } - ErrorResult error; - file->CreateInputStream(getter_AddRefs(imageStream), error); - if (error.Failed()) { - NS_WARNING("File::CreateInputStream() failed"); - return error.StealNSResult(); - } - } else { - imageStream = do_QueryInterface(aObject); - if (NS_WARN_IF(!imageStream)) { - return NS_ERROR_FAILURE; - } - } + } + } + nsCOMPtr object = aObject; + if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) || + type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime)) { + if (nsCOMPtr file = do_QueryInterface(object)) { + object = new FileBlobImpl(file); + // Fallthrough to BlobImpl code below. + } else if (RefPtr blob = do_QueryObject(object)) { + object = blob->Impl(); + // Fallthrough. + } else if (nsCOMPtr imageStream = + do_QueryInterface(object)) { + nsCString imageData; nsresult rv = NS_ConsumeStream(imageStream, UINT32_MAX, imageData); if (NS_FAILED(rv)) { NS_WARNING("NS_ConsumeStream() failed"); @@ -1914,28 +1877,57 @@ nsresult HTMLEditor::InsertObject( NS_WARNING("nsIInputStream::Close() failed"); return rv; } - } - nsAutoString stuffToPaste; - nsresult rv = ImgFromData(type, imageData, stuffToPaste); - if (NS_FAILED(rv)) { - NS_WARNING("ImgFromData() failed"); - return rv; - } + nsAutoString stuffToPaste; + rv = ImgFromData(type, imageData, stuffToPaste); + if (NS_FAILED(rv)) { + NS_WARNING("ImgFromData() failed"); + return rv; + } - AutoPlaceholderBatch treatAsOneTransaction( - *this, ScrollSelectionIntoView::Yes, __FUNCTION__); - rv = InsertHTMLWithContextAsSubAction( - stuffToPaste, u""_ns, u""_ns, NS_LITERAL_STRING_FROM_CSTRING(kFileMime), - aSafeToInsertData, aPointToInsert, aDeleteSelectedContent, - InlineStylesAtInsertionPoint::Preserve); - NS_WARNING_ASSERTION( - NS_SUCCEEDED(rv), - "HTMLEditor::InsertHTMLWithContextAsSubAction(" - "InlineStylesAtInsertionPoint::Preserve) failed, but ignored"); + AutoPlaceholderBatch treatAsOneTransaction( + *this, ScrollSelectionIntoView::Yes, __FUNCTION__); + rv = InsertHTMLWithContextAsSubAction( + stuffToPaste, u""_ns, u""_ns, + NS_LITERAL_STRING_FROM_CSTRING(kFileMime), aSafeToInsertData, + aPointToInsert, aDeleteSelectedContent, + InlineStylesAtInsertionPoint::Preserve); + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "HTMLEditor::InsertHTMLWithContextAsSubAction(" + "InlineStylesAtInsertionPoint::Preserve) failed, but ignored"); + return NS_OK; + } else { + NS_WARNING("HTMLEditor::InsertObject: Unexpected type for image mime"); + return NS_OK; + } } - return NS_OK; + // We always try to insert BlobImpl even without a known image mime. + nsCOMPtr blob = do_QueryInterface(object); + if (!blob) { + return NS_OK; + } + + RefPtr br = new BlobReader( + blob, this, aSafeToInsertData, aPointToInsert, aDeleteSelectedContent); + + nsCOMPtr inner = GetInnerWindow(); + nsCOMPtr global = do_QueryInterface(inner); + if (!global) { + NS_WARNING("Could not get global"); + return NS_ERROR_FAILURE; + } + + RefPtr domBlob = Blob::Create(global, blob); + if (!domBlob) { + NS_WARNING("Blob::Create() failed"); + return NS_ERROR_FAILURE; + } + + nsresult rv = SlurpBlob(domBlob, global, br); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::::SlurpBlob() failed"); + return rv; } static bool GetString(nsISupports* aData, nsAString& aText) { diff --git a/editor/libeditor/tests/test_pasteImgFromTransferable.html b/editor/libeditor/tests/test_pasteImgFromTransferable.html index 2e9e2b5cca9d..a45bb5b574dc 100644 --- a/editor/libeditor/tests/test_pasteImgFromTransferable.html +++ b/editor/libeditor/tests/test_pasteImgFromTransferable.html @@ -60,8 +60,13 @@ add_task(async function() { trans.init(null); trans.setTransferData(test.mimeType, stringStream); + let evt = new Promise(resolve => + edit.addEventListener("input", resolve, {once: true})); + getHTMLEditor(window).pasteTransferable(trans); + await evt; + is(edit.innerHTML, "\"\"", "pastedTransferable pastes image as data URL");