Bug 1778565 - Editor: Handle nsIFile transferable as blob. r=nika,masayuki

Differential Revision: https://phabricator.services.mozilla.com/D155753
This commit is contained in:
Tom Schuster 2022-10-18 17:35:33 +00:00
parent 8a63709ca9
commit 50f59ff179
4 changed files with 86 additions and 111 deletions

View File

@ -8086,28 +8086,6 @@ void nsContentUtils::TransferableToIPCTransferable(
// Otherwise, handle this as a file.
nsCOMPtr<BlobImpl> blobImpl;
if (nsCOMPtr<nsIFile> 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) {

View File

@ -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);
/**

View File

@ -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<nsPIDOMWindowInner> inner = aWindow->GetCurrentInnerWindow();
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(inner);
RefPtr<WeakWorkerRef> workerRef;
RefPtr<FileReader> reader = new FileReader(global, workerRef);
RefPtr<FileReader> reader = new FileReader(aGlobal, workerRef);
RefPtr<SlurpBlobEventListener> eventListener =
new SlurpBlobEventListener(aBlobReader);
@ -1835,74 +1837,35 @@ nsresult HTMLEditor::InsertObject(
DeleteSelectedContent aDeleteSelectedContent) {
MOZ_ASSERT(IsEditActionDataAvailable());
if (nsCOMPtr<BlobImpl> blob = do_QueryInterface(aObject)) {
RefPtr<BlobReader> br = new BlobReader(
blob, this, aSafeToInsertData, aPointToInsert, aDeleteSelectedContent);
// XXX This is not guaranteed.
MOZ_ASSERT(aPointToInsert.IsSet());
RefPtr<Blob> 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<nsIFile> 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<nsIFile> file = do_QueryInterface(aObject)) {
nsCOMPtr<nsIMIMEService> 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<nsIInputStream> imageStream;
if (RefPtr<Blob> blob = do_QueryObject(aObject)) {
RefPtr<File> 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<nsISupports> object = aObject;
if (type.EqualsLiteral(kJPEGImageMime) || type.EqualsLiteral(kJPGImageMime) ||
type.EqualsLiteral(kPNGImageMime) || type.EqualsLiteral(kGIFImageMime)) {
if (nsCOMPtr<nsIFile> file = do_QueryInterface(object)) {
object = new FileBlobImpl(file);
// Fallthrough to BlobImpl code below.
} else if (RefPtr<Blob> blob = do_QueryObject(object)) {
object = blob->Impl();
// Fallthrough.
} else if (nsCOMPtr<nsIInputStream> 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<BlobImpl> blob = do_QueryInterface(object);
if (!blob) {
return NS_OK;
}
RefPtr<BlobReader> br = new BlobReader(
blob, this, aSafeToInsertData, aPointToInsert, aDeleteSelectedContent);
nsCOMPtr<nsPIDOMWindowInner> inner = GetInnerWindow();
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(inner);
if (!global) {
NS_WARNING("Could not get global");
return NS_ERROR_FAILURE;
}
RefPtr<Blob> 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) {

View File

@ -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,
"<img src=\"data:" + test.mimeType + ";base64," + test.base64 + "\" alt=\"\">",
"pastedTransferable pastes image as data URL");