Backed out 2 changesets (bug 1777448) for causing bc failures in /browser_navigator_clipboard_contextmenu_suppression.js CLOSED TREE

Backed out changeset 6dee3dce64ad (bug 1777448)
Backed out changeset 32841cae9b3b (bug 1777448)
This commit is contained in:
Cristian Tuns
2023-12-06 19:55:14 -05:00
parent 880b3168e6
commit 983b4d3824
15 changed files with 96 additions and 585 deletions

View File

@@ -80,7 +80,7 @@ static nsresult AppendDOMNode(nsITransferable* aTransferable,
// copy image as file promise onto the transferable
static nsresult AppendImagePromise(nsITransferable* aTransferable,
imgIRequest* aImgRequest,
nsINode* aImageNode);
nsIImageLoadingContent* aImageElement);
#endif
static nsresult EncodeForTextUnicode(nsIDocumentEncoder& aEncoder,
@@ -244,7 +244,6 @@ static nsresult CreateTransferable(
NS_ENSURE_TRUE(aTransferable, NS_ERROR_NULL_POINTER);
aTransferable->Init(aDocument.GetLoadContext());
aTransferable->SetRequestingPrincipal(aDocument.NodePrincipal());
if (aEncodedDocumentWithContext.mUnicodeEncodingIsTextHTML) {
// Set up a format converter so that clipboard flavor queries work.
// This converter isn't really used for conversions.
@@ -459,14 +458,10 @@ nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
int32_t aCopyFlags) {
nsresult rv;
nsCOMPtr<nsINode> imageNode = do_QueryInterface(aImageElement, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// create a transferable for putting data on the Clipboard
nsCOMPtr<nsITransferable> trans(do_CreateInstance(kCTransferableCID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
trans->Init(aLoadContext);
trans->SetRequestingPrincipal(imageNode->NodePrincipal());
if (aCopyFlags & nsIDocumentViewerEdit::COPY_IMAGE_TEXT) {
// get the location from the element
@@ -509,7 +504,7 @@ nsresult nsCopySupport::ImageCopy(nsIImageLoadingContent* aImageElement,
#ifdef XP_WIN
if (StaticPrefs::clipboard_imageAsFile_enabled()) {
rv = AppendImagePromise(trans, imgRequest, imageNode);
rv = AppendImagePromise(trans, imgRequest, aImageElement);
NS_ENSURE_SUCCESS(rv, rv);
}
#endif
@@ -598,10 +593,10 @@ static nsresult AppendDOMNode(nsITransferable* aTransferable,
#ifdef XP_WIN
static nsresult AppendImagePromise(nsITransferable* aTransferable,
imgIRequest* aImgRequest,
nsINode* aImageNode) {
nsIImageLoadingContent* aImageElement) {
nsresult rv;
NS_ENSURE_TRUE(aImgRequest && aImageNode, NS_OK);
NS_ENSURE_TRUE(aImgRequest, NS_OK);
bool isMultipart;
rv = aImgRequest->GetMultipart(&isMultipart);
@@ -610,6 +605,9 @@ static nsresult AppendImagePromise(nsITransferable* aTransferable,
return NS_OK;
}
nsCOMPtr<nsINode> node = do_QueryInterface(aImageElement, &rv);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
if (NS_WARN_IF(!mimeService)) {
return NS_ERROR_FAILURE;
@@ -645,8 +643,8 @@ static nsresult AppendImagePromise(nsITransferable* aTransferable,
rv = AppendString(aTransferable, validFileName, kFilePromiseDestFilename);
NS_ENSURE_SUCCESS(rv, rv);
aTransferable->SetCookieJarSettings(
aImageNode->OwnerDoc()->CookieJarSettings());
aTransferable->SetRequestingPrincipal(node->NodePrincipal());
aTransferable->SetCookieJarSettings(node->OwnerDoc()->CookieJarSettings());
aTransferable->SetContentPolicyType(nsIContentPolicy::TYPE_INTERNAL_IMAGE);
// add the dataless file promise flavor

View File

@@ -38,7 +38,6 @@
#include "mozilla/dom/DataTransferItemList.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/FileList.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/BindingUtils.h"
@@ -890,17 +889,6 @@ already_AddRefed<nsITransferable> DataTransfer::GetTransferable(
}
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<nsIObjectOutputStream> stream;
@@ -1248,18 +1236,6 @@ void DataTransfer::GetRealFormat(const nsAString& aInFormat,
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,
nsIPrincipal* aPrincipal,
bool aHidden) {

View File

@@ -426,8 +426,6 @@ class DataTransfer final : public nsISupports, public nsWrapperCache {
kImageRequestMime,
kPDFJSMime};
already_AddRefed<nsIGlobalObject> GetGlobal() const;
protected:
// caches text and uri-list data formats that exist in the drag service or
// clipboard for retrieval later.

View File

@@ -15,6 +15,7 @@
#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/DataTransferItemBinding.h"
#include "mozilla/dom/Directory.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/FileSystem.h"
#include "mozilla/dom/FileSystemDirectoryEntry.h"
#include "mozilla/dom/FileSystemFileEntry.h"
@@ -301,7 +302,7 @@ already_AddRefed<File> DataTransferItem::GetAsFile(
if (RefPtr<Blob> blob = do_QueryObject(supports)) {
mCachedFile = blob->ToFile();
} else {
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
if (NS_WARN_IF(!global)) {
return nullptr;
}
@@ -351,7 +352,7 @@ already_AddRefed<FileSystemEntry> DataTransferItem::GetAsEntry(
return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
if (NS_WARN_IF(!global)) {
return nullptr;
}
@@ -427,7 +428,7 @@ already_AddRefed<File> DataTransferItem::CreateFileFromInputStream(
return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal();
nsCOMPtr<nsIGlobalObject> global = GetGlobalFromDataTransfer();
if (NS_WARN_IF(!global)) {
return nullptr;
}
@@ -490,7 +491,18 @@ void DataTransferItem::GetAsString(FunctionStringCallback* aCallback,
RefPtr<GASRunnable> runnable = new GASRunnable(aCallback, stringData);
if (nsCOMPtr<nsIGlobalObject> global = mDataTransfer->GetGlobal()) {
// DataTransfer.mParent might be EventTarget, nsIGlobalObject, ClipboardEvent
// 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());
} else {
rv = NS_DispatchToMainThread(runnable);
@@ -582,4 +594,22 @@ already_AddRefed<nsIVariant> DataTransferItem::Data(nsIPrincipal* aPrincipal,
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

View File

@@ -115,6 +115,8 @@ class DataTransferItem final : public nsISupports, public nsWrapperCache {
nsIInputStream* aStream, const char* aFileNameKey,
const nsAString& aContentType);
already_AddRefed<nsIGlobalObject> GetGlobalFromDataTransfer();
// The index in the 2d mIndexedItems array
uint32_t mIndex;

View File

@@ -12,18 +12,6 @@ skip-if = [
]
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"]
support-files = ["simple_navigator_clipboard_read.html"]
fail-if = ["a11y_checks"] # Bug 1854502 clicked browser may not be accessible

View File

@@ -1,262 +0,0 @@
/* -*- 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();
await Assert.rejects(
readTypesRequest1,
/NotAllowedError/,
"cross-origin request should be rejected"
);
});
});

View File

@@ -1,151 +0,0 @@
/* -*- 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) {
add_task(async function test_context_menu_suppression_ext() {
const extension = await installAndStartExtension(aContentScript);
await BrowserTestUtils.withNewTab(
kContentFileUrl,
async function (browser) {
info(`Write data by keyboard shortcut with custom data`);
const clipboardText = "X" + Math.random();
await SpecialPowers.spawn(browser, [clipboardText], async text => {
let div = content.document.createElement("div");
div.id = "container";
div.innerText = text;
content.document.documentElement.appendChild(div);
});
// trigger keyboard shortcut to copy.
await EventUtils.synthesizeAndWaitKey(
"c",
kIsMac ? { accelKey: true } : { ctrlKey: true }
);
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);
});
});
testExtensionContentScript(() => {
document.addEventListener("copy", function (e) {
e.preventDefault();
let div = document.getElementById("container");
let text = div.innerText;
navigator.clipboard.writeText(text);
});
});

View File

@@ -176,14 +176,20 @@ add_task(async function test_multiple_readText_from_cross_origin_frame() {
"readText() from different origin child frame again before interacting with paste button"
);
const crossOriginFrame = browser.browsingContext.children[1];
await Assert.rejects(
SpecialPowers.spawn(crossOriginFrame, [], async () => {
const readTextRequest2 = SpecialPowers.spawn(
crossOriginFrame,
[],
async () => {
content.document.notifyUserGestureActivation();
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");
await promiseClickPasteButton();
@@ -192,6 +198,11 @@ add_task(async function test_multiple_readText_from_cross_origin_frame() {
clipboardText,
"First request should be resolved"
);
is(
await readTextRequest2,
clipboardText,
"Second request should be resolved"
);
});
});
@@ -215,14 +226,20 @@ add_task(async function test_multiple_readText_from_background_frame() {
info(
"readText() from background tab again before interact with paste button"
);
await Assert.rejects(
SpecialPowers.spawn(backgroundTab.linkedBrowser, [], async () => {
const readTextRequest2 = SpecialPowers.spawn(
backgroundTab.linkedBrowser,
[],
async () => {
content.document.notifyUserGestureActivation();
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(backgroundTab.linkedBrowser, [], async () => {
return new Promise(resolve => {
content.setTimeout(resolve, 0);
});
});
info("Click paste button, both request should be resolved");
await promiseClickPasteButton();
@@ -231,6 +248,11 @@ add_task(async function test_multiple_readText_from_background_frame() {
clipboardText,
"First request should be resolved"
);
is(
await readTextRequest2,
clipboardText,
"Second request should be resolved"
);
});
await BrowserTestUtils.removeTab(backgroundTab);
@@ -248,16 +270,6 @@ add_task(async function test_multiple_readText_from_background_window() {
);
await SimpleTest.promiseFocus(browser);
info("readText() from background window");
await Assert.rejects(
SpecialPowers.spawn(backgroundTab.linkedBrowser, [], async () => {
content.document.notifyUserGestureActivation();
return content.eval(`navigator.clipboard.readText();`);
}),
/NotAllowedError/,
"Request from background window should be rejected"
);
const pasteButtonIsShown = waitForPasteContextMenu();
const readTextRequest1 = SpecialPowers.spawn(browser, [], async () => {
content.document.notifyUserGestureActivation();
@@ -288,29 +300,3 @@ add_task(async function test_multiple_readText_from_background_window() {
await BrowserTestUtils.closeWindow(newWin);
});
});
add_task(async function test_multiple_readText_focuse_in_chrome_document() {
// Randomized text to avoid overlapping with other tests.
const clipboardText = await promiseWritingRandomTextToClipboard();
const win = await BrowserTestUtils.openNewBrowserWindow();
const tab = await BrowserTestUtils.openNewForegroundTab(
win.gBrowser,
kContentFileUrl
);
info("Move focus to url bar");
win.gURLBar.focus();
info("readText() from web content");
await Assert.rejects(
SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
content.document.notifyUserGestureActivation();
return content.eval(`navigator.clipboard.readText();`);
}),
/NotAllowedError/,
"Request should be rejected when focus is not in content"
);
await BrowserTestUtils.closeWindow(win);
});

View File

@@ -100,24 +100,16 @@ function isCloselyLeftOnTopOf(aCoordsP1, aCoordsP2, aDelta = 10) {
);
}
async function promiseDismissPasteButton() {
function promiseDismissPasteButton() {
// nsXULPopupManager rollup is handled in widget code, so we have to
// synthesize native mouse events.
await EventUtils.promiseNativeMouseEvent({
return EventUtils.promiseNativeMouseEvent({
type: "click",
target: document.body,
// Relies on the assumption that the center of chrome document doesn't
// overlay with the paste button showed for clipboard readText request.
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.

View File

@@ -3453,9 +3453,7 @@ mozilla::ipc::IPCResult ContentParent::RecvSetClipboard(
// aRequestingPrincipal is allowed to be nullptr here.
if (!ValidatePrincipal(aTransferable.requestingPrincipal(),
{ValidatePrincipalOptions::AllowNullPtr,
ValidatePrincipalOptions::AllowExpanded,
ValidatePrincipalOptions::AllowSystem})) {
{ValidatePrincipalOptions::AllowNullPtr})) {
LogAndAssertFailedPrincipalValidationInfo(
aTransferable.requestingPrincipal(), __func__);
}

View File

@@ -58,9 +58,7 @@ IPCResult ClipboardWriteRequestParent::RecvSetData(
const IPCTransferable& aTransferable) {
if (!mManager->ValidatePrincipal(
aTransferable.requestingPrincipal(),
{ContentParent::ValidatePrincipalOptions::AllowNullPtr,
ContentParent::ValidatePrincipalOptions::AllowExpanded,
ContentParent::ValidatePrincipalOptions::AllowSystem})) {
{ContentParent::ValidatePrincipalOptions::AllowNullPtr})) {
ContentParent::LogAndAssertFailedPrincipalValidationInfo(
aTransferable.requestingPrincipal(), __func__);
}

View File

@@ -17,7 +17,6 @@
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsContentUtils.h"
#include "nsFocusManager.h"
#include "nsIClipboardOwner.h"
#include "nsIPromptService.h"
#include "nsError.h"
@@ -52,11 +51,9 @@ class UserConfirmationRequest final
UserConfirmationRequest(int32_t aClipboardType,
Document* aRequestingChromeDocument,
nsIPrincipal* aRequestingPrincipal,
nsBaseClipboard* aClipboard)
: mClipboardType(aClipboardType),
mRequestingChromeDocument(aRequestingChromeDocument),
mRequestingPrincipal(aRequestingPrincipal),
mClipboard(aClipboard) {
MOZ_ASSERT(
mClipboard->nsIClipboard::IsClipboardTypeSupported(aClipboardType));
@@ -68,11 +65,10 @@ class UserConfirmationRequest final
void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
mozilla::ErrorResult& aRv) override;
bool IsEqual(int32_t aClipboardType, Document* aRequestingChromeDocument,
nsIPrincipal* aRequestingPrincipal) const {
bool IsEqual(int32_t aClipboardType,
Document* aRequestingChromeDocument) const {
return ClipboardType() == aClipboardType &&
RequestingChromeDocument() == aRequestingChromeDocument &&
RequestingPrincipal()->Equals(aRequestingPrincipal);
RequestingChromeDocument() == aRequestingChromeDocument;
}
int32_t ClipboardType() const { return mClipboardType; }
@@ -81,8 +77,6 @@ class UserConfirmationRequest final
return mRequestingChromeDocument;
}
nsIPrincipal* RequestingPrincipal() const { return mRequestingPrincipal; }
void AddClipboardGetRequest(const nsTArray<nsCString>& aFlavorList,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(!aFlavorList.IsEmpty());
@@ -121,7 +115,6 @@ class UserConfirmationRequest final
const int32_t mClipboardType;
RefPtr<Document> mRequestingChromeDocument;
const nsCOMPtr<nsIPrincipal> mRequestingPrincipal;
const RefPtr<nsBaseClipboard> mClipboard;
// Track the pending read requests that wait for user confirmation.
nsTArray<UniquePtr<ClipboardGetRequest>> mPendingClipboardGetRequests;
@@ -508,22 +501,6 @@ NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
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.
if (aRequestingPrincipal->GetIsAddonOrExpandedAddonPrincipal()) {
MOZ_CLIPBOARD_LOG("%s: Addon without read permission.", __FUNCTION__);
@@ -531,8 +508,7 @@ NS_IMETHODIMP nsBaseClipboard::AsyncGetData(
}
RequestUserConfirmation(aWhichClipboard, aFlavorList,
aRequestingWindowContext, aRequestingPrincipal,
aCallback);
aRequestingWindowContext, aCallback);
return NS_OK;
}
@@ -747,7 +723,6 @@ void nsBaseClipboard::ClearClipboardCache(int32_t aClipboardType) {
void nsBaseClipboard::RequestUserConfirmation(
int32_t aClipboardType, const nsTArray<nsCString>& aFlavorList,
mozilla::dom::WindowContext* aWindowContext,
nsIPrincipal* aRequestingPrincipal,
nsIAsyncClipboardGetCallback* aCallback) {
MOZ_ASSERT(nsIClipboard::IsClipboardTypeSupported(aClipboardType));
MOZ_ASSERT(aCallback);
@@ -759,24 +734,14 @@ void nsBaseClipboard::RequestUserConfirmation(
CanonicalBrowsingContext* cbc =
CanonicalBrowsingContext::Cast(aWindowContext->GetBrowsingContext());
MOZ_ASSERT(
cbc->IsContent(),
"Should not require user confirmation when access from chrome window");
RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
if (!chromeDoc || !chromeDoc->HasFocus(mozilla::IgnoreErrors())) {
MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused window",
__FUNCTION__);
if (!cbc) {
aCallback->OnError(NS_ERROR_FAILURE);
return;
}
mozilla::dom::Element* activeElementInChromeDoc =
chromeDoc->GetActiveElement();
if (activeElementInChromeDoc != cbc->Top()->GetEmbedderElement()) {
// Reject if the request is not from web content that is in the focused tab.
MOZ_CLIPBOARD_LOG("%s: reject due to not in the focused tab", __FUNCTION__);
RefPtr<CanonicalBrowsingContext> chromeTop = cbc->TopCrossChromeBoundary();
Document* chromeDoc = chromeTop ? chromeTop->GetDocument() : nullptr;
if (!chromeDoc) {
aCallback->OnError(NS_ERROR_FAILURE);
return;
}
@@ -784,8 +749,7 @@ void nsBaseClipboard::RequestUserConfirmation(
// If there is a pending user confirmation request, check if we could reuse
// it. If not, reject the request.
if (sUserConfirmationRequest) {
if (sUserConfirmationRequest->IsEqual(aClipboardType, chromeDoc,
aRequestingPrincipal)) {
if (sUserConfirmationRequest->IsEqual(aClipboardType, chromeDoc)) {
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
return;
}
@@ -809,8 +773,8 @@ void nsBaseClipboard::RequestUserConfirmation(
return;
}
sUserConfirmationRequest = new UserConfirmationRequest(
aClipboardType, chromeDoc, aRequestingPrincipal, this);
sUserConfirmationRequest =
new UserConfirmationRequest(aClipboardType, chromeDoc, this);
sUserConfirmationRequest->AddClipboardGetRequest(aFlavorList, aCallback);
promise->AppendNativeHandler(sUserConfirmationRequest);
}

View File

@@ -197,7 +197,6 @@ class nsBaseClipboard : public nsIClipboard {
void RequestUserConfirmation(int32_t aClipboardType,
const nsTArray<nsCString>& aFlavorList,
mozilla::dom::WindowContext* aWindowContext,
nsIPrincipal* aRequestingPrincipal,
nsIAsyncClipboardGetCallback* aCallback);
// Track the pending request for each clipboard type separately. And only need

View File

@@ -194,17 +194,12 @@ interface nsITransferable : nsISupports
[notxpcom, nostdcall] attribute boolean isPrivateData;
/**
* The principal associated with this transferable. This could be either the
* node principal of the source DOM node from which this transferable was
* created, or the principal of the global from which this transferable was
* created.
* XXXedgar: Rename it to something more generic, bug 1867636.
* The principal of the source dom node this transferable was
* created from and the contentPolicyType for the transferable.
* Note, currently only used on Windows for network principal and
* contentPolicyType information in clipboard and drag operations.
*/
[notxpcom, nostdcall] attribute nsIPrincipal requestingPrincipal;
/**
* the contentPolicyType for this transferable.
*/
[notxpcom, nostdcall] attribute nsContentPolicyType contentPolicyType;
/**