Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test runs only with pattern: os == 'win'
- Manifest: toolkit/components/contentanalysis/tests/browser/browser.toml
/* Any copyright is dedicated to the Public Domain.
// Copying out of an alert()/prompt() dialog. These are chrome documents, so
// they are exempt from the content analysis check in nsBaseClipboard::SetData
// and have to opt in via ContentAnalysisUtils -- the same way they already do
// for paste.
"use strict";
const { PromptTestUtils } = ChromeUtils.importESModule(
);
let mockCA = makeMockContentAnalysis();
add_setup(async function test_setup() {
mockCA = await mockContentAnalysisService(mockCA);
await SpecialPowers.pushPrefEnv({
set: [
[
"browser.contentanalysis.interception_point.clipboard_copy.enabled",
true,
],
],
});
});
// Using an external page so the test can check that the URL matches in the
// nsIContentAnalysisRequest.
const PAGE_URL =
const PROMPT_MESSAGE = "Some message from the page";
const PROMPT_DEFAULT_VALUE = "Some default value";
const PREVIOUS_CLIPBOARD_TEXT = "Previous clipboard contents";
// Must match contentanalysis-clipboard-copy-blocked-replacement in
// toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl.
const REPLACEMENT_TEXT =
"Copying this content is restricted by your organization.";
function setClipboardText(clipboardString) {
const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
Ci.nsITransferable
);
trans.init(null);
trans.addDataFlavor("text/plain");
const str = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
str.data = clipboardString;
trans.setTransferData("text/plain", str);
Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
}
function getClipboardText() {
const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
Ci.nsITransferable
);
trans.init(null);
trans.addDataFlavor("text/plain");
let data = {};
try {
Services.clipboard.getData(
trans,
Ci.nsIClipboard.kGlobalClipboard,
window.browsingContext.currentWindowContext
);
trans.getTransferData("text/plain", data);
} catch (e) {
return "";
}
return data.value.QueryInterface(Ci.nsISupportsString).data;
}
function waitForClipboardText(expected) {
return TestUtils.waitForCondition(
() => getClipboardText() === expected,
`waiting for clipboard to contain "${expected}"`
);
}
function assertCopyRequest(request, expectedText) {
is(request.url.spec, PAGE_URL, "request has correct URL");
is(
request.analysisType,
Ci.nsIContentAnalysisRequest.eDataCopied,
"request has correct analysisType"
);
is(
request.reason,
Ci.nsIContentAnalysisRequest.eClipboardCopy,
"request has correct reason"
);
is(
request.operationTypeForDisplay,
Ci.nsIContentAnalysisRequest.eCopyClipboard,
"request has correct operationTypeForDisplay"
);
is(request.textContent, expectedText, "request textContent should match");
ok(request.userActionId.length, "request userActionId should not be empty");
ok(!!request.requestToken.length, "request requestToken should not be empty");
}
// Opens a dialog on PAGE_URL, async runs function aContentFn in its content process
// (which must generate a dialog with alert()/prompt()/etc), passes the content
// process' dialog to aTestFn for testing, and returns the result of aContentFn.
async function withDialog(aContentFn, aTestFn) {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
let browser = tab.linkedBrowser;
try {
let dialogPromise = SpecialPowers.spawn(
browser,
[PROMPT_MESSAGE, PROMPT_DEFAULT_VALUE],
aContentFn
);
let prompt = await PromptTestUtils.waitForPrompt(browser, {
modalType: Services.prompt.MODAL_TYPE_CONTENT,
});
try {
await aTestFn(prompt);
} finally {
await PromptTestUtils.handlePrompt(prompt);
}
return await dialogPromise;
} finally {
BrowserTestUtils.removeTab(tab);
}
}
function withPrompt(aCallback) {
return withDialog(
async (message, defaultValue) => content.prompt(message, defaultValue),
aCallback
);
}
// alert() is the case with no text field at all, so the only thing to copy is
// the page-supplied message.
function withAlert(aCallback) {
return withDialog(async message => content.alert(message), aCallback);
}
// Copying the text the page supplied as prompt()'s default value.
async function testCopyFromPromptTextbox(allowCopy) {
mockCA.setupForTest(allowCopy);
setClipboardText(PREVIOUS_CLIPBOARD_TEXT);
await withPrompt(async prompt => {
prompt.ui.loginTextbox.focus();
prompt.ui.loginTextbox.select();
await EventUtils.synthesizeKey("c", { accelKey: true });
await waitForClipboardText(
allowCopy ? PROMPT_DEFAULT_VALUE : REPLACEMENT_TEXT
);
});
is(mockCA.calls.length, 1, "one call to content analysis");
assertCopyRequest(mockCA.calls[0], PROMPT_DEFAULT_VALUE);
}
add_task(async function testCopyFromPromptTextboxAllow() {
await testCopyFromPromptTextbox(true);
});
add_task(async function testCopyFromPromptTextboxBlock() {
await testCopyFromPromptTextbox(false);
});
// Copying the page-supplied message text out of an alert(). There is no text
// field in this case, so this is the only thing there is to copy.
async function testCopyFromAlertMessage(allowCopy) {
mockCA.setupForTest(allowCopy);
setClipboardText(PREVIOUS_CLIPBOARD_TEXT);
await withAlert(async prompt => {
let infoBody = prompt.ui.infoBody;
is(
infoBody.textContent,
PROMPT_MESSAGE,
"the dialog is showing the page's message"
);
let selection = infoBody.ownerDocument.getSelection();
selection.removeAllRanges();
selection.selectAllChildren(infoBody);
await EventUtils.synthesizeKey(
"c",
{ accelKey: true },
infoBody.ownerGlobal
);
await waitForClipboardText(allowCopy ? PROMPT_MESSAGE : REPLACEMENT_TEXT);
});
is(mockCA.calls.length, 1, "one call to content analysis");
assertCopyRequest(mockCA.calls[0], PROMPT_MESSAGE);
}
add_task(async function testCopyFromAlertMessageAllow() {
await testCopyFromAlertMessage(true);
});
add_task(async function testCopyFromAlertMessageBlock() {
await testCopyFromAlertMessage(false);
});
// The copy event is dispatched at the element holding the start of the
// selection, so selecting from the dialog's title through its message must not
// be a way to slip the message past the check.
add_task(async function testCopySpanningTitleAndMessageIsAnalyzed() {
mockCA.setupForTest(/* shouldAllowRequest */ false);
setClipboardText(PREVIOUS_CLIPBOARD_TEXT);
await withAlert(async prompt => {
let doc = prompt.ui.infoBody.ownerDocument;
let selection = doc.getSelection();
selection.removeAllRanges();
let range = doc.createRange();
range.setStartBefore(prompt.ui.infoTitle);
range.setEndAfter(prompt.ui.infoBody);
selection.addRange(range);
ok(
selection.toString().includes(PROMPT_MESSAGE),
`selection spans the message, got "${selection.toString()}"`
);
await EventUtils.synthesizeKey("c", { accelKey: true }, doc.defaultView);
await waitForClipboardText(REPLACEMENT_TEXT);
});
is(mockCA.calls.length, 1, "one call to content analysis");
ok(
mockCA.calls[0].textContent.includes(PROMPT_MESSAGE),
`the analyzed text includes the message, got "${mockCA.calls[0].textContent}"`
);
});
// Like the content-process path, this one knows the verdict before deciding
// whether to delete, so a blocked cut leaves the text alone.
add_task(async function testBlockedCutFromPromptTextboxKeepsText() {
mockCA.setupForTest(/* shouldAllowRequest */ false);
setClipboardText(PREVIOUS_CLIPBOARD_TEXT);
let result = await withPrompt(async prompt => {
prompt.ui.loginTextbox.focus();
prompt.ui.loginTextbox.select();
await EventUtils.synthesizeKey("x", { accelKey: true });
await waitForClipboardText(REPLACEMENT_TEXT);
is(
prompt.ui.loginTextbox.value,
PROMPT_DEFAULT_VALUE,
"a blocked cut leaves the text field alone"
);
});
is(result, PROMPT_DEFAULT_VALUE, "prompt still returns its original value");
is(mockCA.calls.length, 1, "one call to content analysis");
assertCopyRequest(mockCA.calls[0], PROMPT_DEFAULT_VALUE);
});
add_task(async function testAllowedCutFromPromptTextboxRemovesText() {
mockCA.setupForTest(/* shouldAllowRequest */ true);
setClipboardText(PREVIOUS_CLIPBOARD_TEXT);
let result = await withPrompt(async prompt => {
prompt.ui.loginTextbox.focus();
prompt.ui.loginTextbox.select();
await EventUtils.synthesizeKey("x", { accelKey: true });
await waitForClipboardText(PROMPT_DEFAULT_VALUE);
await TestUtils.waitForCondition(
() => prompt.ui.loginTextbox.value === "",
"an allowed cut removes the text"
);
});
is(result, "", "prompt returns the emptied value");
is(mockCA.calls.length, 1, "one call to content analysis");
});
add_task(async function testCopyFromPromptWithPrefOff() {
await SpecialPowers.pushPrefEnv({
set: [
[
"browser.contentanalysis.interception_point.clipboard_copy.enabled",
false,
],
],
});
mockCA.setupForTest(/* shouldAllowRequest */ false);
setClipboardText(PREVIOUS_CLIPBOARD_TEXT);
await withPrompt(async prompt => {
prompt.ui.loginTextbox.focus();
prompt.ui.loginTextbox.select();
await EventUtils.synthesizeKey("c", { accelKey: true });
// The copy is allowed through without consulting the agent.
await waitForClipboardText(PROMPT_DEFAULT_VALUE);
});
is(
mockCA.calls.length,
0,
"no calls to content analysis when the interception point is off"
);
await SpecialPowers.popPrefEnv();
});