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:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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__);
|
||||
}
|
||||
|
||||
@@ -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__);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user