зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1777448 - Part 2: Suppress the paste context menu when the clipboard data originates from a same-origin page; r=nika
And don't allow new request being associated with existing pending request if the requests are from different origin. This patch also set proper principal to nsItransferable for clipboard write in various cases, - Copy image via context menu. - Copy current selection via keyboard shortcut or context menu. - Data is provided by script when copy operation is triggered via keyboard shortcut or context menu. - Clipboard data is put via async clipboard. Depends on D190761 Differential Revision: https://phabricator.services.mozilla.com/D190796
This commit is contained in:
Родитель
35aaa07f46
Коммит
1b518042e4
|
@ -80,7 +80,7 @@ static nsresult AppendDOMNode(nsITransferable* aTransferable,
|
||||||
// copy image as file promise onto the transferable
|
// copy image as file promise onto the transferable
|
||||||
static nsresult AppendImagePromise(nsITransferable* aTransferable,
|
static nsresult AppendImagePromise(nsITransferable* aTransferable,
|
||||||
imgIRequest* aImgRequest,
|
imgIRequest* aImgRequest,
|
||||||
nsIImageLoadingContent* aImageElement);
|
nsINode* aImageNode);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static nsresult EncodeForTextUnicode(nsIDocumentEncoder& aEncoder,
|
static nsresult EncodeForTextUnicode(nsIDocumentEncoder& aEncoder,
|
||||||
|
@ -244,6 +244,7 @@ static nsresult CreateTransferable(
|
||||||
NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
|
NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
|
||||||
|
|
||||||
aTransferable->Init(aDocument.GetLoadContext());
|
aTransferable->Init(aDocument.GetLoadContext());
|
||||||
|
aTransferable->SetRequestingPrincipal(aDocument.NodePrincipal());
|
||||||
if (aEncodedDocumentWithContext.mUnicodeEncodingIsTextHTML) {
|
if (aEncodedDocumentWithContext.mUnicodeEncodingIsTextHTML) {
|
||||||
// Set up a format converter so that clipboard flavor queries work.
|
// Set up a format converter so that clipboard flavor queries work.
|
||||||
// This converter isn't really used for conversions.
|
// This converter isn't really used for conversions.
|
||||||
|
@ -458,10 +459,14 @@ nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
|
||||||
int32_t aCopyFlags) {
|
int32_t aCopyFlags) {
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
|
|
||||||
|
nsCOMPtr<nsINode> imageNode = do_QueryInterface(aImageElement, &rv);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
// create a transferable for putting data on the Clipboard
|
// create a transferable for putting data on the Clipboard
|
||||||
nsCOMPtr<nsITransferable> trans(do_CreateInstance(kCTransferableCID, &rv));
|
nsCOMPtr<nsITransferable> trans(do_CreateInstance(kCTransferableCID, &rv));
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
trans->Init(aLoadContext);
|
trans->Init(aLoadContext);
|
||||||
|
trans->SetRequestingPrincipal(imageNode->NodePrincipal());
|
||||||
|
|
||||||
if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_TEXT) {
|
if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_TEXT) {
|
||||||
// get the location from the element
|
// get the location from the element
|
||||||
|
@ -504,7 +509,7 @@ nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
|
||||||
|
|
||||||
#ifdef XP_WIN
|
#ifdef XP_WIN
|
||||||
if (StaticPrefs::clipboard_imageAsFile_enabled()) {
|
if (StaticPrefs::clipboard_imageAsFile_enabled()) {
|
||||||
rv = AppendImagePromise(trans, imgRequest, aImageElement);
|
rv = AppendImagePromise(trans, imgRequest, imageNode);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -593,10 +598,10 @@ static nsresult AppendDOMNode(nsITransferable* aTransferable,
|
||||||
#ifdef XP_WIN
|
#ifdef XP_WIN
|
||||||
static nsresult AppendImagePromise(nsITransferable* aTransferable,
|
static nsresult AppendImagePromise(nsITransferable* aTransferable,
|
||||||
imgIRequest* aImgRequest,
|
imgIRequest* aImgRequest,
|
||||||
nsIImageLoadingContent* aImageElement) {
|
nsINode* aImageNode) {
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
|
|
||||||
NS_ENSURE_TRUE(aImgRequest, NS_OK);
|
NS_ENSURE_TRUE(aImgRequest && aImageNode, NS_OK);
|
||||||
|
|
||||||
bool isMultipart;
|
bool isMultipart;
|
||||||
rv = aImgRequest->GetMultipart(&isMultipart);
|
rv = aImgRequest->GetMultipart(&isMultipart);
|
||||||
|
@ -605,9 +610,6 @@ static nsresult AppendImagePromise(nsITransferable* aTransferable,
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsINode> node = do_QueryInterface(aImageElement, &rv);
|
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
|
||||||
|
|
||||||
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
|
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
|
||||||
if (NS_WARN_IF(!mimeService)) {
|
if (NS_WARN_IF(!mimeService)) {
|
||||||
return NS_ERROR_FAILURE;
|
return NS_ERROR_FAILURE;
|
||||||
|
@ -643,8 +645,8 @@ static nsresult AppendImagePromise(nsITransferable* aTransferable,
|
||||||
rv = AppendString(aTransferable, validFileName, kFilePromiseDestFilename);
|
rv = AppendString(aTransferable, validFileName, kFilePromiseDestFilename);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
aTransferable->SetRequestingPrincipal(node->NodePrincipal());
|
aTransferable->SetCookieJarSettings(
|
||||||
aTransferable->SetCookieJarSettings(node->OwnerDoc()->CookieJarSettings());
|
aImageNode->OwnerDoc()->CookieJarSettings());
|
||||||
aTransferable->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE);
|
aTransferable->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE);
|
||||||
|
|
||||||
// add the dataless file promise flavor
|
// add the dataless file promise flavor
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "mozilla/dom/DataTransferItemList.h"
|
#include "mozilla/dom/DataTransferItemList.h"
|
||||||
#include "mozilla/dom/Directory.h"
|
#include "mozilla/dom/Directory.h"
|
||||||
#include "mozilla/dom/Element.h"
|
#include "mozilla/dom/Element.h"
|
||||||
|
#include "mozilla/dom/Event.h"
|
||||||
#include "mozilla/dom/FileList.h"
|
#include "mozilla/dom/FileList.h"
|
||||||
#include "mozilla/dom/IPCBlobUtils.h"
|
#include "mozilla/dom/IPCBlobUtils.h"
|
||||||
#include "mozilla/dom/BindingUtils.h"
|
#include "mozilla/dom/BindingUtils.h"
|
||||||
|
@ -889,6 +890,17 @@ already_AddRefed<nsITransferable> DataTransfer::GetTransferable(
|
||||||
}
|
}
|
||||||
transferable->Init(aLoadContext);
|
transferable->Init(aLoadContext);
|
||||||
|
|
||||||
|
// Set the principal of the global this DataTransfer was created for
|
||||||
|
// on the transferable for ReadWrite events (copy, cut, or dragstart).
|
||||||
|
//
|
||||||
|
// For other events, the data inside the transferable may originate
|
||||||
|
// from another origin or from the OS.
|
||||||
|
if (mMode == Mode::ReadWrite) {
|
||||||
|
if (nsCOMPtr<nsIGlobalObject> global = GetGlobal()) {
|
||||||
|
transferable->SetRequestingPrincipal(global->PrincipalOrNull());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIStorageStream> storageStream;
|
nsCOMPtr<nsIStorageStream> storageStream;
|
||||||
nsCOMPtr<nsIObjectOutputStream> stream;
|
nsCOMPtr<nsIObjectOutputStream> stream;
|
||||||
|
|
||||||
|
@ -1236,6 +1248,18 @@ void DataTransfer::GetRealFormat(const nsAString& aInFormat,
|
||||||
aOutFormat.Assign(lowercaseFormat);
|
aOutFormat.Assign(lowercaseFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
already_AddRefed<nsIGlobalObject> DataTransfer::GetGlobal() const {
|
||||||
|
nsCOMPtr<nsIGlobalObject> global;
|
||||||
|
// This is annoying, but DataTransfer may have various things as parent.
|
||||||
|
if (nsCOMPtr<EventTarget> target = do_QueryInterface(mParent)) {
|
||||||
|
global = target->GetOwnerGlobal();
|
||||||
|
} else if (RefPtr<Event> event = do_QueryObject(mParent)) {
|
||||||
|
global = event->GetParentObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return global.forget();
|
||||||
|
}
|
||||||
|
|
||||||
nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
|
nsresult DataTransfer::CacheExternalData(const char* aFormat, uint32_t aIndex,
|
||||||
nsIPrincipal* aPrincipal,
|
nsIPrincipal* aPrincipal,
|
||||||
bool aHidden) {
|
bool aHidden) {
|
||||||
|
|
|
@ -426,6 +426,8 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
|
||||||
kImageRequestMime,
|
kImageRequestMime,
|
||||||
kPDFJSMime};
|
kPDFJSMime};
|
||||||
|
|
||||||
|
already_AddRefed<nsIGlobalObject> GetGlobal() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// caches text and uri-list data formats that exist in the drag service or
|
// caches text and uri-list data formats that exist in the drag service or
|
||||||
// clipboard for retrieval later.
|
// clipboard for retrieval later.
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
#include "mozilla/dom/BlobImpl.h"
|
#include "mozilla/dom/BlobImpl.h"
|
||||||
#include "mozilla/dom/DataTransferItemBinding.h"
|
#include "mozilla/dom/DataTransferItemBinding.h"
|
||||||
#include "mozilla/dom/Directory.h"
|
#include "mozilla/dom/Directory.h"
|
||||||
#include "mozilla/dom/Event.h"
|
|
||||||
#include "mozilla/dom/FileSystem.h"
|
#include "mozilla/dom/FileSystem.h"
|
||||||
#include "mozilla/dom/FileSystemDirectoryEntry.h"
|
#include "mozilla/dom/FileSystemDirectoryEntry.h"
|
||||||
#include "mozilla/dom/FileSystemFileEntry.h"
|
#include "mozilla/dom/FileSystemFileEntry.h"
|
||||||
|
@ -302,7 +301,7 @@ already_AddRefed<File> DataTransferItem::GetAsFile(
|
||||||
if (RefPtr<Blob> blob = do_QueryObject(supports)) {
|
if (RefPtr<Blob> blob = do_QueryObject(supports)) {
|
||||||
mCachedFile = blob->ToFile();
|
mCachedFile = blob->ToFile();
|
||||||
} else {
|
} else {
|
||||||
nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
|
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
|
||||||
if (NS_WARN_IF(!global)) {
|
if (NS_WARN_IF(!global)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -352,7 +351,7 @@ already_AddRefed<FileSystemEntry> DataTransferItem::GetAsEntry(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
|
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
|
||||||
if (NS_WARN_IF(!global)) {
|
if (NS_WARN_IF(!global)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -428,7 +427,7 @@ already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
|
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
|
||||||
if (NS_WARN_IF(!global)) {
|
if (NS_WARN_IF(!global)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -491,18 +490,7 @@ void DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
|
||||||
|
|
||||||
RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
|
RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
|
||||||
|
|
||||||
// DataTransfer.mParent might be EventTarget, nsIGlobalObject, ClipboardEvent
|
if (nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal()) {
|
||||||
// nsPIDOMWindowOuter, null
|
|
||||||
nsISupports* parent = mDataTransfer->GetParentObject();
|
|
||||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(parent);
|
|
||||||
if (parent && !global) {
|
|
||||||
if (nsCOMPtr<dom::EventTarget> target = do_QueryInterface(parent)) {
|
|
||||||
global = target->GetOwnerGlobal();
|
|
||||||
} else if (RefPtr<Event> event = do_QueryObject(parent)) {
|
|
||||||
global = event->GetParentObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (global) {
|
|
||||||
rv = global->Dispatch(runnable.forget());
|
rv = global->Dispatch(runnable.forget());
|
||||||
} else {
|
} else {
|
||||||
rv = NS_DispatchToMainThread(runnable);
|
rv = NS_DispatchToMainThread(runnable);
|
||||||
|
@ -594,22 +582,4 @@ already_AddRefed<nsIVariant> DataTransferItem::Data(nsIPrincipal* aPrincipal,
|
||||||
return variant.forget();
|
return variant.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<nsIGlobalObject>
|
|
||||||
DataTransferItem::GetGlobalFromDataTransfer() {
|
|
||||||
nsCOMPtr<nsIGlobalObject> global;
|
|
||||||
// This is annoying, but DataTransfer may have various things as parent.
|
|
||||||
nsCOMPtr<EventTarget> target =
|
|
||||||
do_QueryInterface(mDataTransfer->GetParentObject());
|
|
||||||
if (target) {
|
|
||||||
global = target->GetOwnerGlobal();
|
|
||||||
} else {
|
|
||||||
RefPtr<Event> event = do_QueryObject(mDataTransfer->GetParentObject());
|
|
||||||
if (event) {
|
|
||||||
global = event->GetParentObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return global.forget();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mozilla::dom
|
} // namespace mozilla::dom
|
||||||
|
|
|
@ -115,8 +115,6 @@ class DataTransferItem final : public nsISupports, public nsWrapperCache {
|
||||||
nsIInputStream* aStream, const char* aFileNameKey,
|
nsIInputStream* aStream, const char* aFileNameKey,
|
||||||
const nsAString& aContentType);
|
const nsAString& aContentType);
|
||||||
|
|
||||||
already_AddRefed<nsIGlobalObject> GetGlobalFromDataTransfer();
|
|
||||||
|
|
||||||
// The index in the 2d mIndexedItems array
|
// The index in the 2d mIndexedItems array
|
||||||
uint32_t mIndex;
|
uint32_t mIndex;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,18 @@ skip-if = [
|
||||||
]
|
]
|
||||||
support-files = ["simple_navigator_clipboard_keydown.html"]
|
support-files = ["simple_navigator_clipboard_keydown.html"]
|
||||||
|
|
||||||
|
["browser_navigator_clipboard_contextmenu_suppression.js"]
|
||||||
|
support-files = [
|
||||||
|
"file_toplevel.html",
|
||||||
|
"file_iframe.html",
|
||||||
|
]
|
||||||
|
|
||||||
|
["browser_navigator_clipboard_contextmenu_suppression_ext.js"]
|
||||||
|
support-files = [
|
||||||
|
"file_toplevel.html",
|
||||||
|
"file_iframe.html",
|
||||||
|
]
|
||||||
|
|
||||||
["browser_navigator_clipboard_read.js"]
|
["browser_navigator_clipboard_read.js"]
|
||||||
support-files = ["simple_navigator_clipboard_read.html"]
|
support-files = ["simple_navigator_clipboard_read.html"]
|
||||||
fail-if = ["a11y_checks"] # Bug 1854502 clicked browser may not be accessible
|
fail-if = ["a11y_checks"] # Bug 1854502 clicked browser may not be accessible
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
/* -*- Mode: JavaScript; 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
|
const kBaseUrlForContent = getRootDirectory(gTestPath).replace(
|
||||||
|
"chrome://mochitests/content",
|
||||||
|
"https://example.com"
|
||||||
|
);
|
||||||
|
const kContentFileName = "file_toplevel.html";
|
||||||
|
const kContentFileUrl = kBaseUrlForContent + kContentFileName;
|
||||||
|
const kIsMac = navigator.platform.indexOf("Mac") > -1;
|
||||||
|
|
||||||
|
async function waitForPasteContextMenu() {
|
||||||
|
await waitForPasteMenuPopupEvent("shown");
|
||||||
|
let pasteButton = document.getElementById(kPasteMenuItemId);
|
||||||
|
info("Wait for paste button enabled");
|
||||||
|
await BrowserTestUtils.waitForMutationCondition(
|
||||||
|
pasteButton,
|
||||||
|
{ attributeFilter: ["disabled"] },
|
||||||
|
() => !pasteButton.disabled,
|
||||||
|
"Wait for paste button enabled"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readText(aBrowser) {
|
||||||
|
return SpecialPowers.spawn(aBrowser, [], async () => {
|
||||||
|
content.document.notifyUserGestureActivation();
|
||||||
|
return content.eval(`navigator.clipboard.readText();`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function testPasteContextMenuSuppression(aWriteFun, aMsg) {
|
||||||
|
add_task(async function test_context_menu_suppression_sameorigin() {
|
||||||
|
await BrowserTestUtils.withNewTab(
|
||||||
|
kContentFileUrl,
|
||||||
|
async function (browser) {
|
||||||
|
info(`Write data by ${aMsg}`);
|
||||||
|
let clipboardText = await aWriteFun(browser);
|
||||||
|
|
||||||
|
info("Test read from same-origin frame");
|
||||||
|
let listener = function (e) {
|
||||||
|
if (e.target.getAttribute("id") == kPasteMenuPopupId) {
|
||||||
|
ok(false, "paste contextmenu should not be shown");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("popupshown", listener);
|
||||||
|
is(
|
||||||
|
await readText(browser.browsingContext.children[0]),
|
||||||
|
clipboardText,
|
||||||
|
"read should just be resolved without paste contextmenu shown"
|
||||||
|
);
|
||||||
|
document.removeEventListener("popupshown", listener);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_context_menu_suppression_crossorigin() {
|
||||||
|
await BrowserTestUtils.withNewTab(
|
||||||
|
kContentFileUrl,
|
||||||
|
async function (browser) {
|
||||||
|
info(`Write data by ${aMsg}`);
|
||||||
|
let clipboardText = await aWriteFun(browser);
|
||||||
|
|
||||||
|
info("Test read from cross-origin frame");
|
||||||
|
let pasteButtonIsShown = waitForPasteContextMenu();
|
||||||
|
let readTextRequest = readText(browser.browsingContext.children[1]);
|
||||||
|
await pasteButtonIsShown;
|
||||||
|
|
||||||
|
info("Click paste button, request should be resolved");
|
||||||
|
await promiseClickPasteButton();
|
||||||
|
is(await readTextRequest, clipboardText, "Request should be resolved");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_context_menu_suppression_multiple() {
|
||||||
|
await BrowserTestUtils.withNewTab(
|
||||||
|
kContentFileUrl,
|
||||||
|
async function (browser) {
|
||||||
|
info(`Write data by ${aMsg}`);
|
||||||
|
let clipboardText = await aWriteFun(browser);
|
||||||
|
|
||||||
|
info("Test read from cross-origin frame");
|
||||||
|
let pasteButtonIsShown = waitForPasteContextMenu();
|
||||||
|
let readTextRequest1 = readText(browser.browsingContext.children[1]);
|
||||||
|
await pasteButtonIsShown;
|
||||||
|
|
||||||
|
info(
|
||||||
|
"Test read from same-origin frame before paste contextmenu is closed"
|
||||||
|
);
|
||||||
|
is(
|
||||||
|
await readText(browser.browsingContext.children[0]),
|
||||||
|
clipboardText,
|
||||||
|
"read from same-origin should just be resolved without showing paste contextmenu shown"
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Dismiss paste button, cross-origin request should be rejected");
|
||||||
|
await promiseDismissPasteButton();
|
||||||
|
await Assert.rejects(
|
||||||
|
readTextRequest1,
|
||||||
|
/NotAllowedError/,
|
||||||
|
"cross-origin request should be rejected"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_setup(async function () {
|
||||||
|
await SpecialPowers.pushPrefEnv({
|
||||||
|
set: [
|
||||||
|
["dom.events.asyncClipboard.readText", true],
|
||||||
|
["dom.events.asyncClipboard.clipboardItem", true],
|
||||||
|
["test.events.async.enabled", true],
|
||||||
|
// Avoid paste button delay enabling making test too long.
|
||||||
|
["security.dialog_enable_delay", 0],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testPasteContextMenuSuppression(async aBrowser => {
|
||||||
|
const clipboardText = "X" + Math.random();
|
||||||
|
await SpecialPowers.spawn(aBrowser, [clipboardText], async text => {
|
||||||
|
content.document.notifyUserGestureActivation();
|
||||||
|
return content.eval(`navigator.clipboard.writeText("${text}");`);
|
||||||
|
});
|
||||||
|
return clipboardText;
|
||||||
|
}, "clipboard.writeText()");
|
||||||
|
|
||||||
|
testPasteContextMenuSuppression(async aBrowser => {
|
||||||
|
const clipboardText = "X" + Math.random();
|
||||||
|
await SpecialPowers.spawn(aBrowser, [clipboardText], async text => {
|
||||||
|
content.document.notifyUserGestureActivation();
|
||||||
|
return content.eval(`
|
||||||
|
const itemInput = new ClipboardItem({["text/plain"]: "${text}"});
|
||||||
|
navigator.clipboard.write([itemInput]);
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
return clipboardText;
|
||||||
|
}, "clipboard.write()");
|
||||||
|
|
||||||
|
testPasteContextMenuSuppression(async aBrowser => {
|
||||||
|
const clipboardText = "X" + Math.random();
|
||||||
|
await SpecialPowers.spawn(aBrowser, [clipboardText], async text => {
|
||||||
|
let div = content.document.createElement("div");
|
||||||
|
div.innerText = text;
|
||||||
|
content.document.documentElement.appendChild(div);
|
||||||
|
// select text
|
||||||
|
content
|
||||||
|
.getSelection()
|
||||||
|
.setBaseAndExtent(div.firstChild, text.length, div.firstChild, 0);
|
||||||
|
});
|
||||||
|
// trigger keyboard shortcut to copy.
|
||||||
|
await EventUtils.synthesizeAndWaitKey(
|
||||||
|
"c",
|
||||||
|
kIsMac ? { accelKey: true } : { ctrlKey: true }
|
||||||
|
);
|
||||||
|
return clipboardText;
|
||||||
|
}, "keyboard shortcut");
|
||||||
|
|
||||||
|
testPasteContextMenuSuppression(async aBrowser => {
|
||||||
|
const clipboardText = "X" + Math.random();
|
||||||
|
await SpecialPowers.spawn(aBrowser, [clipboardText], async text => {
|
||||||
|
return content.eval(`
|
||||||
|
document.addEventListener("copy", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.clipboardData.setData("text/plain", "${text}");
|
||||||
|
}, { once: true });
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
// trigger keyboard shortcut to copy.
|
||||||
|
await EventUtils.synthesizeAndWaitKey(
|
||||||
|
"c",
|
||||||
|
kIsMac ? { accelKey: true } : { ctrlKey: true }
|
||||||
|
);
|
||||||
|
return clipboardText;
|
||||||
|
}, "keyboard shortcut with custom data");
|
||||||
|
|
||||||
|
testPasteContextMenuSuppression(async aBrowser => {
|
||||||
|
const clipboardText = "X" + Math.random();
|
||||||
|
await SpecialPowers.spawn(aBrowser, [clipboardText], async text => {
|
||||||
|
let div = content.document.createElement("div");
|
||||||
|
div.innerText = text;
|
||||||
|
content.document.documentElement.appendChild(div);
|
||||||
|
// select text
|
||||||
|
content
|
||||||
|
.getSelection()
|
||||||
|
.setBaseAndExtent(div.firstChild, text.length, div.firstChild, 0);
|
||||||
|
return SpecialPowers.doCommand(content, "cmd_copy");
|
||||||
|
});
|
||||||
|
return clipboardText;
|
||||||
|
}, "copy command");
|
||||||
|
|
||||||
|
async function readTypes(aBrowser) {
|
||||||
|
return SpecialPowers.spawn(aBrowser, [], async () => {
|
||||||
|
content.document.notifyUserGestureActivation();
|
||||||
|
let items = await content.eval(`navigator.clipboard.read();`);
|
||||||
|
return items[0].types;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_context_menu_suppression_image() {
|
||||||
|
await BrowserTestUtils.withNewTab(kContentFileUrl, async function (browser) {
|
||||||
|
await SpecialPowers.spawn(browser, [], async () => {
|
||||||
|
let image = content.document.createElement("img");
|
||||||
|
let copyImagePromise = new Promise(resolve => {
|
||||||
|
image.addEventListener(
|
||||||
|
"load",
|
||||||
|
e => {
|
||||||
|
let documentViewer = content.docShell.docViewer.QueryInterface(
|
||||||
|
SpecialPowers.Ci.nsIDocumentViewerEdit
|
||||||
|
);
|
||||||
|
documentViewer.setCommandNode(image);
|
||||||
|
documentViewer.copyImage(documentViewer.COPY_IMAGE_ALL);
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
image.src =
|
||||||
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAD4AAABHCAIAAADQjmMaAA" +
|
||||||
|
"AACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3goUAwAgSAORBwAAABl0RVh0Q29tbW" +
|
||||||
|
"VudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABPSURBVGje7c4BDQAACAOga//OmuMbJG" +
|
||||||
|
"AurTbq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6u" +
|
||||||
|
"rq6s31B0IqAY2/tQVCAAAAAElFTkSuQmCC";
|
||||||
|
content.document.documentElement.appendChild(image);
|
||||||
|
await copyImagePromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Test read from cross-origin frame");
|
||||||
|
let pasteButtonIsShown = waitForPasteContextMenu();
|
||||||
|
let readTypesRequest1 = readTypes(browser.browsingContext.children[1]);
|
||||||
|
await pasteButtonIsShown;
|
||||||
|
|
||||||
|
info("Test read from same-origin frame before paste contextmenu is closed");
|
||||||
|
const clipboarCacheEnabled = SpecialPowers.getBoolPref(
|
||||||
|
"widget.clipboard.use-cached-data.enabled",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
// If the cached data is used, it uses type order in cached transferable.
|
||||||
|
SimpleTest.isDeeply(
|
||||||
|
await readTypes(browser.browsingContext.children[0]),
|
||||||
|
clipboarCacheEnabled
|
||||||
|
? ["text/plain", "text/html", "image/png"]
|
||||||
|
: ["text/html", "text/plain", "image/png"],
|
||||||
|
"read from same-origin should just be resolved without showing paste contextmenu shown"
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Dismiss paste button, cross-origin request should be rejected");
|
||||||
|
await promiseDismissPasteButton();
|
||||||
|
// XXX edgar: not sure why first promiseDismissPasteButton doesn't work on Windows opt build.
|
||||||
|
await promiseDismissPasteButton();
|
||||||
|
await Assert.rejects(
|
||||||
|
readTypesRequest1,
|
||||||
|
/NotAllowedError/,
|
||||||
|
"cross-origin request should be rejected"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,156 @@
|
||||||
|
/* -*- Mode: JavaScript; 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
|
const kBaseUrlForContent = getRootDirectory(gTestPath).replace(
|
||||||
|
"chrome://mochitests/content",
|
||||||
|
"https://example.com"
|
||||||
|
);
|
||||||
|
const kContentFileName = "file_toplevel.html";
|
||||||
|
const kContentFileUrl = kBaseUrlForContent + kContentFileName;
|
||||||
|
const kIsMac = navigator.platform.indexOf("Mac") > -1;
|
||||||
|
|
||||||
|
async function waitForPasteContextMenu() {
|
||||||
|
await waitForPasteMenuPopupEvent("shown");
|
||||||
|
let pasteButton = document.getElementById(kPasteMenuItemId);
|
||||||
|
info("Wait for paste button enabled");
|
||||||
|
await BrowserTestUtils.waitForMutationCondition(
|
||||||
|
pasteButton,
|
||||||
|
{ attributeFilter: ["disabled"] },
|
||||||
|
() => !pasteButton.disabled,
|
||||||
|
"Wait for paste button enabled"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readText(aBrowser) {
|
||||||
|
return SpecialPowers.spawn(aBrowser, [], async () => {
|
||||||
|
content.document.notifyUserGestureActivation();
|
||||||
|
return content.eval(`navigator.clipboard.readText();`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testPasteContextMenu(
|
||||||
|
aBrowser,
|
||||||
|
aClipboardText,
|
||||||
|
aShouldShow = true
|
||||||
|
) {
|
||||||
|
let pasteButtonIsShown;
|
||||||
|
if (aShouldShow) {
|
||||||
|
pasteButtonIsShown = waitForPasteContextMenu();
|
||||||
|
}
|
||||||
|
let readTextRequest = readText(aBrowser);
|
||||||
|
if (aShouldShow) {
|
||||||
|
await pasteButtonIsShown;
|
||||||
|
}
|
||||||
|
|
||||||
|
info("Click paste button, request should be resolved");
|
||||||
|
if (aShouldShow) {
|
||||||
|
await promiseClickPasteButton();
|
||||||
|
}
|
||||||
|
is(await readTextRequest, aClipboardText, "Request should be resolved");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installAndStartExtension(aContentScript) {
|
||||||
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
|
manifest: {
|
||||||
|
content_scripts: [
|
||||||
|
{
|
||||||
|
js: ["content_script.js"],
|
||||||
|
matches: ["https://example.com/*/file_toplevel.html"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
"content_script.js": aContentScript,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await extension.startup();
|
||||||
|
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testExtensionContentScript(aContentScript, aMessage) {
|
||||||
|
add_task(async function test_context_menu_suppression_ext() {
|
||||||
|
info(`${aMessage}`);
|
||||||
|
const extension = await installAndStartExtension(aContentScript);
|
||||||
|
await BrowserTestUtils.withNewTab(
|
||||||
|
kContentFileUrl,
|
||||||
|
async function (browser) {
|
||||||
|
const clipboardText = "X" + Math.random();
|
||||||
|
await SpecialPowers.spawn(browser, [clipboardText], async text => {
|
||||||
|
info(`Set clipboard text to ${text}`);
|
||||||
|
let div = content.document.createElement("div");
|
||||||
|
div.id = "container";
|
||||||
|
div.innerText = text;
|
||||||
|
content.document.documentElement.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
|
let writePromise = extension.awaitMessage("write-data-ready");
|
||||||
|
// trigger keyboard shortcut to copy.
|
||||||
|
await EventUtils.synthesizeAndWaitKey(
|
||||||
|
"c",
|
||||||
|
kIsMac ? { accelKey: true } : { ctrlKey: true }
|
||||||
|
);
|
||||||
|
// Wait a bit for clipboard write.
|
||||||
|
await writePromise;
|
||||||
|
|
||||||
|
info("Test read from same frame");
|
||||||
|
await testPasteContextMenu(browser, clipboardText, false);
|
||||||
|
|
||||||
|
info("Test read from same-origin subframe");
|
||||||
|
await testPasteContextMenu(
|
||||||
|
browser.browsingContext.children[0],
|
||||||
|
clipboardText,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Test read from cross-origin subframe");
|
||||||
|
await testPasteContextMenu(
|
||||||
|
browser.browsingContext.children[1],
|
||||||
|
clipboardText,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await extension.unload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_setup(async function () {
|
||||||
|
await SpecialPowers.pushPrefEnv({
|
||||||
|
set: [
|
||||||
|
["dom.events.asyncClipboard.readText", true],
|
||||||
|
["dom.events.asyncClipboard.clipboardItem", true],
|
||||||
|
["test.events.async.enabled", true],
|
||||||
|
// Avoid paste button delay enabling making test too long.
|
||||||
|
["security.dialog_enable_delay", 0],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testExtensionContentScript(() => {
|
||||||
|
document.addEventListener("copy", function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let div = document.getElementById("container");
|
||||||
|
let text = div.innerText;
|
||||||
|
e.clipboardData.setData("text/plain", text);
|
||||||
|
browser.test.sendMessage("write-data-ready");
|
||||||
|
});
|
||||||
|
}, "Write data by DataTransfer API in extension");
|
||||||
|
|
||||||
|
testExtensionContentScript(() => {
|
||||||
|
document.addEventListener("copy", async function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
let div = document.getElementById("container");
|
||||||
|
let text = div.innerText;
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
browser.test.sendMessage("write-data-ready");
|
||||||
|
});
|
||||||
|
}, "Write data by Async Clipboard API in extension");
|
|
@ -176,20 +176,14 @@ add_task(async function test_multiple_readText_from_cross_origin_frame() {
|
||||||
"readText() from different origin child frame again before interacting with paste button"
|
"readText() from different origin child frame again before interacting with paste button"
|
||||||
);
|
);
|
||||||
const crossOriginFrame = browser.browsingContext.children[1];
|
const crossOriginFrame = browser.browsingContext.children[1];
|
||||||
const readTextRequest2 = SpecialPowers.spawn(
|
await Assert.rejects(
|
||||||
crossOriginFrame,
|
SpecialPowers.spawn(crossOriginFrame, [], async () => {
|
||||||
[],
|
|
||||||
async () => {
|
|
||||||
content.document.notifyUserGestureActivation();
|
content.document.notifyUserGestureActivation();
|
||||||
return content.eval(`navigator.clipboard.readText();`);
|
return content.eval(`navigator.clipboard.readText();`);
|
||||||
}
|
}),
|
||||||
|
/NotAllowedError/,
|
||||||
|
"Second request should be rejected"
|
||||||
);
|
);
|
||||||
// Give some time for the second request to arrive parent process.
|
|
||||||
await SpecialPowers.spawn(crossOriginFrame, [], async () => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
content.setTimeout(resolve, 0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
info("Click paste button, both request should be resolved");
|
info("Click paste button, both request should be resolved");
|
||||||
await promiseClickPasteButton();
|
await promiseClickPasteButton();
|
||||||
|
@ -198,11 +192,6 @@ add_task(async function test_multiple_readText_from_cross_origin_frame() {
|
||||||
clipboardText,
|
clipboardText,
|
||||||
"First request should be resolved"
|
"First request should be resolved"
|
||||||
);
|
);
|
||||||
is(
|
|
||||||
await readTextRequest2,
|
|
||||||
clipboardText,
|
|
||||||
"Second request should be resolved"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -100,16 +100,24 @@ function isCloselyLeftOnTopOf(aCoordsP1, aCoordsP2, aDelta = 10) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function promiseDismissPasteButton() {
|
async function promiseDismissPasteButton() {
|
||||||
// nsXULPopupManager rollup is handled in widget code, so we have to
|
// nsXULPopupManager rollup is handled in widget code, so we have to
|
||||||
// synthesize native mouse events.
|
// synthesize native mouse events.
|
||||||
return EventUtils.promiseNativeMouseEvent({
|
await EventUtils.promiseNativeMouseEvent({
|
||||||
type: "click",
|
type: "click",
|
||||||
target: document.body,
|
target: document.body,
|
||||||
// Relies on the assumption that the center of chrome document doesn't
|
// Relies on the assumption that the center of chrome document doesn't
|
||||||
// overlay with the paste button showed for clipboard readText request.
|
// overlay with the paste button showed for clipboard readText request.
|
||||||
atCenter: true,
|
atCenter: true,
|
||||||
});
|
});
|
||||||
|
// Move mouse away to avoid subsequence tests showing paste button in
|
||||||
|
// thie dismissing location.
|
||||||
|
await EventUtils.promiseNativeMouseEvent({
|
||||||
|
type: "mousemove",
|
||||||
|
target: document.body,
|
||||||
|
offsetX: 100,
|
||||||
|
offsetY: 100,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// @param aBrowser browser object of the content tab.
|
// @param aBrowser browser object of the content tab.
|
||||||
|
|
|
@ -3453,7 +3453,9 @@ mozilla::ipc::IPCResult ContentParent::RecvSetClipboard(
|
||||||
// aRequestingPrincipal is allowed to be nullptr here.
|
// aRequestingPrincipal is allowed to be nullptr here.
|
||||||
|
|
||||||
if (!ValidatePrincipal(aTransferable.requestingPrincipal(),
|
if (!ValidatePrincipal(aTransferable.requestingPrincipal(),
|
||||||
{ValidatePrincipalOptions::AllowNullPtr})) {
|
{ValidatePrincipalOptions::AllowNullPtr,
|
||||||
|
ValidatePrincipalOptions::AllowExpanded,
|
||||||
|
ValidatePrincipalOptions::AllowSystem})) {
|
||||||
LogAndAssertFailedPrincipalValidationInfo(
|
LogAndAssertFailedPrincipalValidationInfo(
|
||||||
aTransferable.requestingPrincipal(), __func__);
|
aTransferable.requestingPrincipal(), __func__);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,9 @@ IPCResult ClipboardWriteRequestParent::RecvSetData(
|
||||||
const IPCTransferable& aTransferable) {
|
const IPCTransferable& aTransferable) {
|
||||||
if (!mManager->ValidatePrincipal(
|
if (!mManager->ValidatePrincipal(
|
||||||
aTransferable.requestingPrincipal(),
|
aTransferable.requestingPrincipal(),
|
||||||
{ContentParent::ValidatePrincipalOptions::AllowNullPtr})) {
|
{ContentParent::ValidatePrincipalOptions::AllowNullPtr,
|
||||||
|
ContentParent::ValidatePrincipalOptions::AllowExpanded,
|
||||||
|
ContentParent::ValidatePrincipalOptions::AllowSystem})) {
|
||||||
ContentParent::LogAndAssertFailedPrincipalValidationInfo(
|
ContentParent::LogAndAssertFailedPrincipalValidationInfo(
|
||||||
aTransferable.requestingPrincipal(), __func__);
|
aTransferable.requestingPrincipal(), __func__);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,11 @@ class UserConfirmationRequest final
|
||||||
|
|
||||||
UserConfirmationRequest(int32_t aClipboardType,
|
UserConfirmationRequest(int32_t aClipboardType,
|
||||||
Document* aRequestingChromeDocument,
|
Document* aRequestingChromeDocument,
|
||||||
|
nsIPrincipal* aRequestingPrincipal,
|
||||||
nsBaseClipboard* aClipboard)
|
nsBaseClipboard* aClipboard)
|
||||||
: mClipboardType(aClipboardType),
|
: mClipboardType(aClipboardType),
|
||||||
mRequestingChromeDocument(aRequestingChromeDocument),
|
mRequestingChromeDocument(aRequestingChromeDocument),
|
||||||
|
mRequestingPrincipal(aRequestingPrincipal),
|
||||||
mClipboard(aClipboard) {
|
mClipboard(aClipboard) {
|
||||||
MOZ_ASSERT(
|
MOZ_ASSERT(
|
||||||
mClipboard->nsIClipboard::IsClipboardTypeSupported(aClipboardType));
|
mClipboard->nsIClipboard::IsClipboardTypeSupported(aClipboardType));
|
||||||
|
@ -66,10 +68,11 @@ class UserConfirmationRequest final
|
||||||
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
|
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
|
||||||
mozilla::ErrorResult& aRv) override;
|
mozilla::ErrorResult& aRv) override;
|
||||||
|
|
||||||
bool IsEqual(int32_t aClipboardType,
|
bool IsEqual(int32_t aClipboardType, Document* aRequestingChromeDocument,
|
||||||
Document* aRequestingChromeDocument) const {
|
nsIPrincipal* aRequestingPrincipal) const {
|
||||||
return ClipboardType() == aClipboardType &&
|
return ClipboardType() == aClipboardType &&
|
||||||
RequestingChromeDocument() == aRequestingChromeDocument;
|
RequestingChromeDocument() == aRequestingChromeDocument &&
|
||||||
|
RequestingPrincipal()->Equals(aRequestingPrincipal);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t ClipboardType() const { return mClipboardType; }
|
int32_t ClipboardType() const { return mClipboardType; }
|
||||||
|
@ -78,6 +81,8 @@ class UserConfirmationRequest final
|
||||||
return mRequestingChromeDocument;
|
return mRequestingChromeDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nsIPrincipal* RequestingPrincipal() const { return mRequestingPrincipal; }
|
||||||
|
|
||||||
void AddClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
|
void AddClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
|
||||||
nsIAsyncClipboardGetCallback* aCallback) {
|
nsIAsyncClipboardGetCallback* aCallback) {
|
||||||
MOZ_ASSERT(!aFlavorList.IsEmpty());
|
MOZ_ASSERT(!aFlavorList.IsEmpty());
|
||||||
|
@ -116,6 +121,7 @@ class UserConfirmationRequest final
|
||||||
|
|
||||||
const int32_t mClipboardType;
|
const int32_t mClipboardType;
|
||||||
RefPtr<Document> mRequestingChromeDocument;
|
RefPtr<Document> mRequestingChromeDocument;
|
||||||
|
const nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
|
||||||
const RefPtr<nsBaseClipboard> mClipboard;
|
const RefPtr<nsBaseClipboard> mClipboard;
|
||||||
// Track the pending read requests that wait for user confirmation.
|
// Track the pending read requests that wait for user confirmation.
|
||||||
nsTArray<UniquePtr<ClipboardGetRequest>> mPendingClipboardGetRequests;
|
nsTArray<UniquePtr<ClipboardGetRequest>> mPendingClipboardGetRequests;
|
||||||
|
@ -502,6 +508,22 @@ NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If cache data is valid, we are the last ones to put something on the native
|
||||||
|
// clipboard, then check if the data is from the same-origin page,
|
||||||
|
if (auto* clipboardCache = GetClipboardCacheIfValid(aWhichClipboard)) {
|
||||||
|
nsCOMPtr<nsITransferable> trans = clipboardCache->GetTransferable();
|
||||||
|
MOZ_ASSERT(trans);
|
||||||
|
|
||||||
|
if (nsCOMPtr<nsIPrincipal> principal = trans->GetRequestingPrincipal()) {
|
||||||
|
if (aRequestingPrincipal->Subsumes(principal)) {
|
||||||
|
MOZ_CLIPBOARD_LOG("%s: native clipboard data is from same-origin page.",
|
||||||
|
__FUNCTION__);
|
||||||
|
AsyncGetDataInternal(aFlavorList, aWhichClipboard, aCallback);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: enable showing the "Paste" button in this case; see bug 1773681.
|
// TODO: enable showing the "Paste" button in this case; see bug 1773681.
|
||||||
if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
|
if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
|
||||||
MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
|
MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
|
||||||
|
@ -509,7 +531,8 @@ NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestUserConfirmation(aWhichClipboard, aFlavorList,
|
RequestUserConfirmation(aWhichClipboard, aFlavorList,
|
||||||
aRequestingWindowContext, aCallback);
|
aRequestingWindowContext, aRequestingPrincipal,
|
||||||
|
aCallback);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,6 +747,7 @@ void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
|
||||||
void nsBaseClipboard::RequestUserConfirmation(
|
void nsBaseClipboard::RequestUserConfirmation(
|
||||||
int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
|
int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
|
||||||
mozilla::dom::WindowContext* aWindowContext,
|
mozilla::dom::WindowContext* aWindowContext,
|
||||||
|
nsIPrincipal* aRequestingPrincipal,
|
||||||
nsIAsyncClipboardGetCallback* aCallback) {
|
nsIAsyncClipboardGetCallback* aCallback) {
|
||||||
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
|
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
|
||||||
MOZ_ASSERT(aCallback);
|
MOZ_ASSERT(aCallback);
|
||||||
|
@ -760,7 +784,8 @@ void nsBaseClipboard::RequestUserConfirmation(
|
||||||
// If there is a pending user confirmation request, check if we could reuse
|
// If there is a pending user confirmation request, check if we could reuse
|
||||||
// it. If not, reject the request.
|
// it. If not, reject the request.
|
||||||
if (sUserConfirmationRequest) {
|
if (sUserConfirmationRequest) {
|
||||||
if (sUserConfirmationRequest->IsEqual(aClipboardType, chromeDoc)) {
|
if (sUserConfirmationRequest->IsEqual(aClipboardType, chromeDoc,
|
||||||
|
aRequestingPrincipal)) {
|
||||||
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
|
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -784,8 +809,8 @@ void nsBaseClipboard::RequestUserConfirmation(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sUserConfirmationRequest =
|
sUserConfirmationRequest = new UserConfirmationRequest(
|
||||||
new UserConfirmationRequest(aClipboardType, chromeDoc, this);
|
aClipboardType, chromeDoc, aRequestingPrincipal, this);
|
||||||
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
|
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
|
||||||
promise->AppendNativeHandler(sUserConfirmationRequest);
|
promise->AppendNativeHandler(sUserConfirmationRequest);
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,6 +197,7 @@ class nsBaseClipboard : public nsIClipboard {
|
||||||
void RequestUserConfirmation(int32_t aClipboardType,
|
void RequestUserConfirmation(int32_t aClipboardType,
|
||||||
const nsTArray<nsCString>& aFlavorList,
|
const nsTArray<nsCString>& aFlavorList,
|
||||||
mozilla::dom::WindowContext* aWindowContext,
|
mozilla::dom::WindowContext* aWindowContext,
|
||||||
|
nsIPrincipal* aRequestingPrincipal,
|
||||||
nsIAsyncClipboardGetCallback* aCallback);
|
nsIAsyncClipboardGetCallback* aCallback);
|
||||||
|
|
||||||
// Track the pending request for each clipboard type separately. And only need
|
// Track the pending request for each clipboard type separately. And only need
|
||||||
|
|
|
@ -194,12 +194,17 @@ interface nsITransferable : nsISupports
|
||||||
[notxpcom, nostdcall] attribute boolean isPrivateData;
|
[notxpcom, nostdcall] attribute boolean isPrivateData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The principal of the source dom node this transferable was
|
* The principal associated with this transferable. This could be either the
|
||||||
* created from and the contentPolicyType for the transferable.
|
* node principal of the source DOM node from which this transferable was
|
||||||
* Note, currently only used on Windows for network principal and
|
* created, or the principal of the global from which this transferable was
|
||||||
* contentPolicyType information in clipboard and drag operations.
|
* created.
|
||||||
|
* XXXedgar: Rename it to something more generic, bug 1867636.
|
||||||
*/
|
*/
|
||||||
[notxpcom, nostdcall] attribute nsIPrincipal requestingPrincipal;
|
[notxpcom, nostdcall] attribute nsIPrincipal requestingPrincipal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the contentPolicyType for this transferable.
|
||||||
|
*/
|
||||||
[notxpcom, nostdcall] attribute nsContentPolicyType contentPolicyType;
|
[notxpcom, nostdcall] attribute nsContentPolicyType contentPolicyType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Загрузка…
Ссылка в новой задаче