diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /toolkit/components/contentanalysis/tests/browser | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz firefox-8dd16259287f58f9273002717ec4d27e97127719.zip |
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/contentanalysis/tests/browser')
16 files changed, 1472 insertions, 134 deletions
diff --git a/toolkit/components/contentanalysis/tests/browser/browser.toml b/toolkit/components/contentanalysis/tests/browser/browser.toml index bdbf350593..7a9a3533d1 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser.toml +++ b/toolkit/components/contentanalysis/tests/browser/browser.toml @@ -4,6 +4,33 @@ support-files = [ "head.js", ] +["browser_clipboard_content_analysis.js"] + +["browser_clipboard_paste_file_content_analysis.js"] +support-files = [ + "clipboard_paste_file.html", +] + +["browser_clipboard_paste_inputandtextarea_content_analysis.js"] +support-files = [ + "clipboard_paste_inputandtextarea.html", +] + +["browser_clipboard_paste_noformatting_content_analysis.js"] +support-files = [ + "clipboard_paste_noformatting.html", +] + +["browser_clipboard_paste_prompt_content_analysis.js"] +support-files = [ + "clipboard_paste_prompt.html", +] + +["browser_clipboard_read_async_content_analysis.js"] +support-files = [ + "clipboard_read_async.html", +] + ["browser_content_analysis_policies.js"] ["browser_print_changing_page_content_analysis.js"] diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js new file mode 100644 index 0000000000..875dbaa6e3 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js @@ -0,0 +1,363 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This test is used to check copy and paste in editable areas to ensure that non-text +// types (html and images) are copied to and pasted from the clipboard properly. + +var testPage = + "<body style='margin: 0'>" + + " <img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" + + " <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" + + "</body>"; + +let mockCA = makeMockContentAnalysis(); + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +async function testClipboardWithContentAnalysis(allowPaste) { + mockCA.setupForTest(allowPaste); + let tab = BrowserTestUtils.addTab(gBrowser); + let browser = gBrowser.getBrowserForTab(tab); + + gBrowser.selectedTab = tab; + + await promiseTabLoadEvent(tab, "data:text/html," + escape(testPage)); + await SimpleTest.promiseFocus(browser); + + function sendKey(key, code) { + return BrowserTestUtils.synthesizeKey( + key, + { code, accelKey: true }, + browser + ); + } + + // On windows, HTML clipboard includes extra data. + // The values are from widget/windows/nsDataObj.cpp. + const htmlPrefix = navigator.platform.includes("Win") + ? "<html><body>\n<!--StartFragment-->" + : ""; + const htmlPostfix = navigator.platform.includes("Win") + ? "<!--EndFragment-->\n</body>\n</html>" + : ""; + + await SpecialPowers.spawn(browser, [], () => { + var doc = content.document; + var main = doc.getElementById("main"); + main.focus(); + + // Select an area of the text. + let selection = doc.getSelection(); + selection.modify("move", "left", "line"); + selection.modify("move", "right", "character"); + selection.modify("move", "right", "character"); + selection.modify("move", "right", "character"); + selection.modify("extend", "right", "word"); + selection.modify("extend", "right", "word"); + }); + + // The data is empty as the selection was copied during the event default phase. + let copyEventPromise = BrowserTestUtils.waitForContentEvent( + browser, + "copy", + false, + event => { + return event.clipboardData.mozItemCount == 0; + } + ); + await SpecialPowers.spawn(browser, [], () => {}); + await sendKey("c"); + await copyEventPromise; + + let pastePromise = SpecialPowers.spawn( + browser, + [htmlPrefix, htmlPostfix, allowPaste], + (htmlPrefixChild, htmlPostfixChild, allowPaste) => { + let selection = content.document.getSelection(); + selection.modify("move", "right", "line"); + + return new Promise((resolve, _reject) => { + content.addEventListener( + "paste", + event => { + let clipboardData = event.clipboardData; + Assert.equal( + clipboardData.mozItemCount, + 1, + "One item on clipboard" + ); + Assert.equal( + clipboardData.types.length, + 2, + "Two types on clipboard" + ); + Assert.equal( + clipboardData.types[0], + "text/html", + "text/html on clipboard" + ); + Assert.equal( + clipboardData.types[1], + "text/plain", + "text/plain on clipboard" + ); + Assert.equal( + clipboardData.getData("text/html"), + allowPaste + ? htmlPrefixChild + "t <b>Bold</b>" + htmlPostfixChild + : "", + "text/html value" + ); + Assert.equal( + clipboardData.getData("text/plain"), + allowPaste ? "t Bold" : "", + "text/plain value" + ); + resolve(); + }, + { capture: true, once: true } + ); + }); + } + ); + + await SpecialPowers.spawn(browser, [], () => {}); + + await sendKey("v"); + await pastePromise; + // 2 calls because there are two formats on the clipboard + is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest( + mockCA.calls[0], + htmlPrefix + "t <b>Bold</b>" + htmlPostfix + ); + assertContentAnalysisRequest(mockCA.calls[1], "t Bold"); + mockCA.clearCalls(); + + let copyPromise = SpecialPowers.spawn(browser, [], () => { + var main = content.document.getElementById("main"); + + Assert.equal( + main.innerHTML, + "Test <b>Bold</b> After Textt <b>Bold</b>", + "Copy and paste html" + ); + + let selection = content.document.getSelection(); + selection.modify("extend", "left", "word"); + selection.modify("extend", "left", "word"); + selection.modify("extend", "left", "character"); + + return new Promise((resolve, _reject) => { + content.addEventListener( + "cut", + event => { + event.clipboardData.setData("text/plain", "Some text"); + event.clipboardData.setData("text/html", "<i>Italic</i> "); + selection.deleteFromDocument(); + event.preventDefault(); + resolve(); + }, + { capture: true, once: true } + ); + }); + }); + + await SpecialPowers.spawn(browser, [], () => {}); + + await sendKey("x"); + await copyPromise; + + pastePromise = SpecialPowers.spawn( + browser, + [htmlPrefix, htmlPostfix, allowPaste], + (htmlPrefixChild, htmlPostfixChild, allowPaste) => { + let selection = content.document.getSelection(); + selection.modify("move", "left", "line"); + + return new Promise((resolve, _reject) => { + content.addEventListener( + "paste", + event => { + let clipboardData = event.clipboardData; + Assert.equal( + clipboardData.mozItemCount, + 1, + "One item on clipboard 2" + ); + Assert.equal( + clipboardData.types.length, + 2, + "Two types on clipboard 2" + ); + Assert.equal( + clipboardData.types[0], + "text/html", + "text/html on clipboard 2" + ); + Assert.equal( + clipboardData.types[1], + "text/plain", + "text/plain on clipboard 2" + ); + Assert.equal( + clipboardData.getData("text/html"), + allowPaste + ? htmlPrefixChild + "<i>Italic</i> " + htmlPostfixChild + : "", + "text/html value 2" + ); + Assert.equal( + clipboardData.getData("text/plain"), + allowPaste ? "Some text" : "", + "text/plain value 2" + ); + resolve(); + }, + { capture: true, once: true } + ); + }); + } + ); + + await SpecialPowers.spawn(browser, [], () => {}); + + await sendKey("v"); + await pastePromise; + // 2 calls because there are two formats on the clipboard + is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest( + mockCA.calls[0], + htmlPrefix + "<i>Italic</i> " + htmlPostfix + ); + assertContentAnalysisRequest(mockCA.calls[1], "Some text"); + mockCA.clearCalls(); + + await SpecialPowers.spawn(browser, [], () => { + var main = content.document.getElementById("main"); + Assert.equal( + main.innerHTML, + "<i>Italic</i> Test <b>Bold</b> After<b></b>", + "Copy and paste html 2" + ); + }); + + // Next, check that the Copy Image command works. + + // The context menu needs to be opened to properly initialize for the copy + // image command to run. + let contextMenu = document.getElementById("contentAreaContextMenu"); + let contextMenuShown = promisePopupShown(contextMenu); + BrowserTestUtils.synthesizeMouseAtCenter( + "#img", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser + ); + await contextMenuShown; + + document.getElementById("context-copyimage-contents").doCommand(); + + contextMenu.hidePopup(); + await promisePopupHidden(contextMenu); + + // Focus the content again + await SimpleTest.promiseFocus(browser); + + pastePromise = SpecialPowers.spawn( + browser, + [htmlPrefix, htmlPostfix, allowPaste], + (htmlPrefixChild, htmlPostfixChild, allowPaste) => { + var doc = content.document; + var main = doc.getElementById("main"); + main.focus(); + + return new Promise((resolve, reject) => { + content.addEventListener( + "paste", + event => { + let clipboardData = event.clipboardData; + + // DataTransfer doesn't support the image types yet, so only text/html + // will be present. + let clipboardText = clipboardData.getData("text/html"); + if (allowPaste) { + if ( + clipboardText !== + htmlPrefixChild + + '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' + + htmlPostfixChild + ) { + reject( + "Clipboard Data did not contain an image, was " + + clipboardText + ); + } + } else if (clipboardText !== "") { + reject("Clipboard Data should be empty, was " + clipboardText); + } + resolve(); + }, + { capture: true, once: true } + ); + }); + } + ); + + await SpecialPowers.spawn(browser, [], () => {}); + await sendKey("v"); + await pastePromise; + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest( + mockCA.calls[0], + htmlPrefix + + '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' + + htmlPostfix + ); + mockCA.clearCalls(); + + // The new content should now include an image. + await SpecialPowers.spawn(browser, [], () => { + var main = content.document.getElementById("main"); + Assert.equal( + main.innerHTML, + '<i>Italic</i> <img id="img" tabindex="1" ' + + 'src="http://example.org/browser/browser/base/content/test/general/moz.png">' + + "Test <b>Bold</b> After<b></b>", + "Paste after copy image" + ); + }); + + gBrowser.removeCurrentTab(); +} + +function assertContentAnalysisRequest(request, expectedText) { + is( + request.url.spec, + "data:text/html," + escape(testPage), + "request has correct URL" + ); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.eBulkDataEntry, + "request has correct analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eClipboard, + "request has correct operationTypeForDisplay" + ); + is(request.filePath, "", "request filePath should be empty"); + is(request.textContent, expectedText, "request textContent should match"); + is(request.printDataHandle, 0, "request printDataHandle should not be 0"); + is(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} +add_task(async function testClipboardWithContentAnalysisAllow() { + await testClipboardWithContentAnalysis(true); +}); + +add_task(async function testClipboardWithContentAnalysisBlock() { + await testClipboardWithContentAnalysis(false); +}); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js new file mode 100644 index 0000000000..8441c4d7fd --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js @@ -0,0 +1,202 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that (real) files can be pasted into chrome/content. +// Pasting files should also hide all other data from content. + +function setClipboard(path) { + const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(path); + + const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(null); + trans.addDataFlavor("application/x-moz-file"); + trans.setTransferData("application/x-moz-file", file); + + trans.addDataFlavor("text/plain"); + const str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + str.data = "Alternate"; + trans.setTransferData("text/plain", str); + + // Write to clipboard. + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); +} + +let mockCA = makeMockContentAnalysis(); + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const PAGE_URL = + "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html"; + +function assertContentAnalysisRequest( + request, + expectedDisplayType, + expectedFilePath, + expectedText +) { + is(request.url.spec, PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.eBulkDataEntry, + "request has correct analysisType" + ); + is( + request.operationTypeForDisplay, + expectedDisplayType, + "request has correct operationTypeForDisplay" + ); + is(request.filePath, expectedFilePath, "request filePath should match"); + is(request.textContent, expectedText, "request textContent should match"); + is(request.printDataHandle, 0, "request printDataHandle should not be 0"); + is(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} +function assertContentAnalysisRequestFile(request, expectedFilePath) { + assertContentAnalysisRequest( + request, + Ci.nsIContentAnalysisRequest.eCustomDisplayString, + expectedFilePath, + "" + ); +} +function assertContentAnalysisRequestText(request, expectedText) { + assertContentAnalysisRequest( + request, + Ci.nsIContentAnalysisRequest.eClipboard, + "", + expectedText + ); +} + +async function testClipboardPasteFileWithContentAnalysis(allowPaste) { + mockCA.setupForTest(allowPaste); + await SpecialPowers.pushPrefEnv({ + set: [["dom.events.dataTransfer.mozFile.enabled", true]], + }); + + // Create a temporary file that will be pasted. + const file = await IOUtils.createUniqueFile( + PathUtils.tempDir, + "test-file.txt", + 0o600 + ); + const FILE_TEXT = "Hello World!"; + await IOUtils.writeUTF8(file, FILE_TEXT); + + // Put the data directly onto the native clipboard to make sure + // it isn't handled internally in Gecko somehow. + setClipboard(file); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + let browser = tab.linkedBrowser; + + let resultPromise = SpecialPowers.spawn(browser, [allowPaste], allowPaste => { + return new Promise(resolve => { + content.document.addEventListener("testresult", event => { + resolve(event.detail.result); + }); + content.document.getElementById("pasteAllowed").checked = allowPaste; + }); + }); + + // Focus <input> in content + await SpecialPowers.spawn(browser, [], async function () { + content.document.getElementById("input").focus(); + }); + + // Paste file into <input> in content + await BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser); + + let result = await resultPromise; + is( + result, + allowPaste ? PathUtils.filename(file) : "", + "Correctly pasted file in content" + ); + is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequestFile(mockCA.calls[0], file); + assertContentAnalysisRequestText(mockCA.calls[1], "Alternate"); + mockCA.clearCalls(); + + // The following part of the test is done in-process (note the use of document here instead of + // content.document) so none of this should go through content analysis. + var input = document.createElement("input"); + document.documentElement.appendChild(input); + input.focus(); + + await new Promise((resolve, _reject) => { + input.addEventListener( + "paste", + function (event) { + let dt = event.clipboardData; + is(dt.types.length, 3, "number of types"); + ok(dt.types.includes("text/plain"), "text/plain exists in types"); + ok( + dt.types.includes("application/x-moz-file"), + "application/x-moz-file exists in types" + ); + is(dt.types[2], "Files", "Last type should be 'Files'"); + ok( + dt.mozTypesAt(0).contains("text/plain"), + "text/plain exists in mozTypesAt" + ); + is( + dt.getData("text/plain"), + "Alternate", + "text/plain returned in getData" + ); + is( + dt.mozGetDataAt("text/plain", 0), + "Alternate", + "text/plain returned in mozGetDataAt" + ); + + ok( + dt.mozTypesAt(0).contains("application/x-moz-file"), + "application/x-moz-file exists in mozTypesAt" + ); + let mozFile = dt.mozGetDataAt("application/x-moz-file", 0); + + ok( + mozFile instanceof Ci.nsIFile, + "application/x-moz-file returned nsIFile with mozGetDataAt" + ); + + is( + mozFile.leafName, + PathUtils.filename(file), + "nsIFile has correct leafName" + ); + + is(mozFile.fileSize, FILE_TEXT.length, "nsIFile has correct length"); + + resolve(); + }, + { capture: true, once: true } + ); + + EventUtils.synthesizeKey("v", { accelKey: true }); + }); + is(mockCA.calls.length, 0, "Correct number of calls to Content Analysis"); + + input.remove(); + + BrowserTestUtils.removeTab(tab); + + await IOUtils.remove(file); +} + +add_task(async function testClipboardPasteFileWithContentAnalysisAllow() { + await testClipboardPasteFileWithContentAnalysis(true); +}); + +add_task(async function testClipboardPasteFileWithContentAnalysisBlock() { + await testClipboardPasteFileWithContentAnalysis(false); +}); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js new file mode 100644 index 0000000000..6fe37c3368 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let mockCA = makeMockContentAnalysis(); + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const PAGE_URL = + "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html"; +const CLIPBOARD_TEXT_STRING = "Just some text"; +async function testClipboardPaste(allowPaste) { + mockCA.setupForTest(allowPaste); + + setClipboardData(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + let browser = tab.linkedBrowser; + + await SpecialPowers.spawn(browser, [allowPaste], async allowPaste => { + content.document.getElementById("pasteAllowed").checked = allowPaste; + }); + await testPasteWithElementId("testInput", browser, allowPaste); + await testPasteWithElementId("testTextArea", browser, allowPaste); + + BrowserTestUtils.removeTab(tab); +} + +function setClipboardData() { + 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 = CLIPBOARD_TEXT_STRING; + trans.setTransferData("text/plain", str); + + // Write to clipboard. + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); +} + +async function testPasteWithElementId(elementId, browser, allowPaste) { + let resultPromise = SpecialPowers.spawn(browser, [], () => { + return new Promise(resolve => { + content.document.addEventListener( + "testresult", + event => { + resolve(event.detail.result); + }, + { once: true } + ); + }); + }); + + // Paste into content + await SpecialPowers.spawn(browser, [elementId], async elementId => { + content.document.getElementById(elementId).focus(); + }); + await BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser); + let result = await resultPromise; + is(result, undefined, "Got unexpected result from page"); + + // Because we call event.clipboardData.getData in the test, this causes another call to + // content analysis. + is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING); + assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING); + mockCA.clearCalls(); + let value = await getElementValue(browser, elementId); + is( + value, + allowPaste ? CLIPBOARD_TEXT_STRING : "", + "element has correct value" + ); +} + +function assertContentAnalysisRequest(request, expectedText) { + is(request.url.spec, PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.eBulkDataEntry, + "request has correct analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eClipboard, + "request has correct operationTypeForDisplay" + ); + is(request.filePath, "", "request filePath should match"); + is(request.textContent, expectedText, "request textContent should match"); + is(request.printDataHandle, 0, "request printDataHandle should not be 0"); + is(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +async function getElementValue(browser, elementId) { + return await SpecialPowers.spawn(browser, [elementId], async elementId => { + return content.document.getElementById(elementId).value; + }); +} + +add_task(async function testClipboardPasteWithContentAnalysisAllow() { + await testClipboardPaste(true); +}); + +add_task(async function testClipboardPasteWithContentAnalysisBlock() { + await testClipboardPaste(false); +}); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js new file mode 100644 index 0000000000..0eedcddd17 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let mockCA = makeMockContentAnalysis(); + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const PAGE_URL = + "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html"; +async function testClipboardPasteNoFormatting(allowPaste) { + mockCA.setupForTest(allowPaste); + + const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( + Ci.nsITransferable + ); + trans.init(null); + const CLIPBOARD_TEXT_STRING = "Some text"; + const CLIPBOARD_HTML_STRING = "<b>Some HTML</b>"; + { + trans.addDataFlavor("text/plain"); + const str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + str.data = CLIPBOARD_TEXT_STRING; + trans.setTransferData("text/plain", str); + } + { + trans.addDataFlavor("text/html"); + const str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + str.data = CLIPBOARD_HTML_STRING; + trans.setTransferData("text/html", str); + } + + // Write to clipboard. + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + let browser = tab.linkedBrowser; + let result = await SpecialPowers.spawn(browser, [allowPaste], allowPaste => { + return new Promise(resolve => { + content.document.addEventListener("testresult", event => { + resolve(event.detail.result); + }); + content.document.getElementById("pasteAllowed").checked = allowPaste; + content.document.dispatchEvent(new content.CustomEvent("teststart", {})); + }); + }); + is(result, true, "Got unexpected result from page"); + + // Because we call event.clipboardData.getData in the test, this causes another call to + // content analysis. + is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING); + assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING); + + BrowserTestUtils.removeTab(tab); +} + +function assertContentAnalysisRequest(request, expectedText) { + is(request.url.spec, PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.eBulkDataEntry, + "request has correct analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eClipboard, + "request has correct operationTypeForDisplay" + ); + is(request.filePath, "", "request filePath should match"); + is(request.textContent, expectedText, "request textContent should match"); + is(request.printDataHandle, 0, "request printDataHandle should not be 0"); + is(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +add_task( + async function testClipboardPasteNoFormattingWithContentAnalysisAllow() { + await testClipboardPasteNoFormatting(true); + } +); + +add_task( + async function testClipboardPasteNoFormattingWithContentAnalysisBlock() { + await testClipboardPasteNoFormatting(false); + } +); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js new file mode 100644 index 0000000000..9e57250cc7 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js @@ -0,0 +1,92 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { PromptTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromptTestUtils.sys.mjs" +); + +let mockCA = makeMockContentAnalysis(); + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +// Using an external page so the test can checks that the URL matches in the nsIContentAnalysisRequest +const PAGE_URL = + "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html"; +const CLIPBOARD_TEXT_STRING = "Just some text"; +async function testClipboardPaste(allowPaste) { + mockCA.setupForTest(allowPaste); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + let browser = tab.linkedBrowser; + + let promptPromise = SpecialPowers.spawn(browser, [], async () => { + return content.prompt(); + }); + + let prompt = await PromptTestUtils.waitForPrompt(browser, { + modalType: Services.prompt.MODAL_TYPE_CONTENT, + }); + // Paste text into prompt() in content + let pastePromise = new Promise(resolve => { + prompt.ui.loginTextbox.addEventListener( + "paste", + () => { + // Since mockCA uses setTimeout before invoking the callback, + // do it here too + setTimeout(() => { + resolve(); + }, 0); + }, + { once: true } + ); + }); + let ev = new ClipboardEvent("paste", { + dataType: "text/plain", + data: CLIPBOARD_TEXT_STRING, + }); + prompt.ui.loginTextbox.dispatchEvent(ev); + await pastePromise; + + // Close the prompt + await PromptTestUtils.handlePrompt(prompt); + + let result = await promptPromise; + is( + result, + allowPaste ? CLIPBOARD_TEXT_STRING : "", + "prompt has correct value" + ); + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING); + + BrowserTestUtils.removeTab(tab); +} + +function assertContentAnalysisRequest(request, expectedText) { + is(request.url.spec, PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.eBulkDataEntry, + "request has correct analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eClipboard, + "request has correct operationTypeForDisplay" + ); + is(request.filePath, null, "request filePath should match"); + is(request.textContent, expectedText, "request textContent should match"); + is(request.printDataHandle, 0, "request printDataHandle should not be 0"); + is(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +add_task(async function testClipboardPasteWithContentAnalysisAllow() { + await testClipboardPaste(true); +}); + +add_task(async function testClipboardPasteWithContentAnalysisBlock() { + await testClipboardPaste(false); +}); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js new file mode 100644 index 0000000000..7d180a048b --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js @@ -0,0 +1,195 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +let mockCA = makeMockContentAnalysis(); + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.events.asyncClipboard.readText", true], + // This pref turns off the "Paste" popup + ["dom.events.testing.asyncClipboard", true], + ], + }); +}); + +const PAGE_URL = + "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html"; +const CLIPBOARD_TEXT_STRING = "Some plain text"; +const CLIPBOARD_HTML_STRING = "<b>Some HTML</b>"; +async function testClipboardReadAsync(allowPaste) { + mockCA.setupForTest(allowPaste); + + setClipboardData(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + let browser = tab.linkedBrowser; + { + let result = await setDataAndStartTest(browser, allowPaste, "read"); + is(result, true, "Got unexpected result from page for read()"); + + is( + mockCA.calls.length, + 2, + "Correct number of calls to Content Analysis for read()" + ); + // On Windows, widget adds extra data into HTML clipboard. + let expectedHtml = navigator.platform.includes("Win") + ? `<html><body>\n<!--StartFragment-->${CLIPBOARD_HTML_STRING}<!--EndFragment-->\n</body>\n</html>` + : CLIPBOARD_HTML_STRING; + + assertContentAnalysisRequest(mockCA.calls[0], expectedHtml); + assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING); + mockCA.clearCalls(); + } + + { + let result = await setDataAndStartTest(browser, allowPaste, "readText"); + is(result, true, "Got unexpected result from page for readText()"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis for read()" + ); + assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING); + mockCA.clearCalls(); + } + + BrowserTestUtils.removeTab(tab); +} + +async function testClipboardReadAsyncWithErrorHelper() { + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + setClipboardData(); + + // This test throws a number of exceptions, so tell the framework this is OK. + // If an exception is thrown we won't get the right response from setDataAndStartTest() + // so this should be safe to do. + ignoreAllUncaughtExceptions(); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL); + let browser = tab.linkedBrowser; + { + let result = await setDataAndStartTest(browser, false, "read", true); + is(result, true, "Got unexpected result from page for read()"); + + is( + mockCA.calls.length, + 2, + "Correct number of calls to Content Analysis for read()" + ); + // On Windows, widget adds extra data into HTML clipboard. + let expectedHtml = navigator.platform.includes("Win") + ? `<html><body>\n<!--StartFragment-->${CLIPBOARD_HTML_STRING}<!--EndFragment-->\n</body>\n</html>` + : CLIPBOARD_HTML_STRING; + + assertContentAnalysisRequest(mockCA.calls[0], expectedHtml); + assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING); + mockCA.clearCalls(); + } + + { + let result = await setDataAndStartTest(browser, false, "readText", true); + is(result, true, "Got unexpected result from page for readText()"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis for read()" + ); + assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING); + mockCA.clearCalls(); + } + + BrowserTestUtils.removeTab(tab); +} + +function setDataAndStartTest( + browser, + allowPaste, + testType, + shouldError = false +) { + return SpecialPowers.spawn( + browser, + [allowPaste, testType, shouldError], + (allowPaste, testType, shouldError) => { + return new Promise(resolve => { + content.document.addEventListener( + "testresult", + event => { + resolve(event.detail.result); + }, + { once: true } + ); + content.document.getElementById("pasteAllowed").checked = allowPaste; + content.document.getElementById("contentAnalysisReturnsError").checked = + shouldError; + content.document.dispatchEvent( + new content.CustomEvent("teststart", { + detail: Cu.cloneInto({ testType }, content), + }) + ); + }); + } + ); +} + +function assertContentAnalysisRequest(request, expectedText) { + is(request.url.spec, PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.eBulkDataEntry, + "request has correct analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eClipboard, + "request has correct operationTypeForDisplay" + ); + is(request.filePath, "", "request filePath should match"); + is(request.textContent, expectedText, "request textContent should match"); + is(request.printDataHandle, 0, "request printDataHandle should not be 0"); + is(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +function setClipboardData() { + 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 = CLIPBOARD_TEXT_STRING; + trans.setTransferData("text/plain", str); + } + { + trans.addDataFlavor("text/html"); + const str = Cc["@mozilla.org/supports-string;1"].createInstance( + Ci.nsISupportsString + ); + str.data = CLIPBOARD_HTML_STRING; + trans.setTransferData("text/html", str); + } + + // Write to clipboard. + Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); +} + +add_task(async function testClipboardReadAsyncWithContentAnalysisAllow() { + await testClipboardReadAsync(true); +}); + +add_task(async function testClipboardReadAsyncWithContentAnalysisBlock() { + await testClipboardReadAsync(false); +}); + +add_task(async function testClipboardReadAsyncWithError() { + await testClipboardReadAsyncWithErrorHelper(); +}); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js index b226c0a37a..e7122508e1 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js +++ b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js @@ -25,7 +25,8 @@ const kAgentNamePref = "agent_name"; const kClientSignaturePref = "client_signature"; const kPerUserPref = "is_per_user"; const kShowBlockedPref = "show_blocked_result"; -const kDefaultAllowPref = "default_allow"; +const kDefaultResultPref = "default_result"; +const kBypassForSameTabOperationsPref = "bypass_for_same_tab_operations"; const ca = Cc["@mozilla.org/contentanalysis;1"].getService( Ci.nsIContentAnalysis @@ -87,7 +88,8 @@ add_task(async function test_ca_enterprise_config() { ClientSignature: string4, IsPerUser: true, ShowBlockedResult: false, - DefaultAllow: true, + DefaultResult: 1, + BypassForSameTabOperations: true, }, }, }); @@ -135,9 +137,16 @@ add_task(async function test_ca_enterprise_config() { "show blocked match" ); is( - Services.prefs.getBoolPref("browser.contentanalysis." + kDefaultAllowPref), + Services.prefs.getIntPref("browser.contentanalysis." + kDefaultResultPref), + 1, + "default result match" + ); + is( + Services.prefs.getBoolPref( + "browser.contentanalysis." + kBypassForSameTabOperationsPref + ), true, - "default allow match" + "bypass for same tab operations match" ); PoliciesPrefTracker.stop(); }); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js index 72a7dcbb91..0f2d846627 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js +++ b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js @@ -12,69 +12,7 @@ const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService( Ci.nsIPrintSettingsService ); -let mockCA = { - isActive: true, - mightBeActive: true, - errorValue: undefined, - - setupForTest(shouldAllowRequest) { - this.shouldAllowRequest = shouldAllowRequest; - this.errorValue = undefined; - this.calls = []; - }, - - setupForTestWithError(errorValue) { - this.errorValue = errorValue; - this.calls = []; - }, - - getAction() { - if (this.shouldAllowRequest === undefined) { - this.shouldAllowRequest = true; - } - return this.shouldAllowRequest - ? Ci.nsIContentAnalysisResponse.eAllow - : Ci.nsIContentAnalysisResponse.eBlock; - }, - - // nsIContentAnalysis methods - async analyzeContentRequest(request, _autoAcknowledge) { - info( - "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" + - this.shouldAllowRequest + - ", this.errorValue=" + - this.errorValue - ); - this.calls.push(request); - if (this.errorValue) { - throw this.errorValue; - } - // Use setTimeout to simulate an async activity - await new Promise(res => setTimeout(res, 0)); - return makeContentAnalysisResponse(this.getAction(), request.requestToken); - }, - - analyzeContentRequestCallback(request, autoAcknowledge, callback) { - info( - "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" + - this.shouldAllowRequest + - ", this.errorValue=" + - this.errorValue - ); - this.calls.push(request); - if (this.errorValue) { - throw this.errorValue; - } - let response = makeContentAnalysisResponse( - this.getAction(), - request.requestToken - ); - // Use setTimeout to simulate an async activity - setTimeout(() => { - callback.contentResult(response); - }, 0); - }, -}; +let mockCA = makeMockContentAnalysis(); add_setup(async function test_setup() { mockCA = mockContentAnalysisService(mockCA); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js index 9b4c0ffa60..05897b5ca6 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js +++ b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js @@ -12,73 +12,7 @@ const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService( Ci.nsIPrintSettingsService ); -let mockCA = { - isActive: true, - mightBeActive: true, - errorValue: undefined, - - setupForTest(shouldAllowRequest) { - this.shouldAllowRequest = shouldAllowRequest; - this.errorValue = undefined; - this.calls = []; - }, - - setupForTestWithError(errorValue) { - this.errorValue = errorValue; - this.calls = []; - }, - - clearCalls() { - this.calls = []; - }, - - getAction() { - if (this.shouldAllowRequest === undefined) { - this.shouldAllowRequest = true; - } - return this.shouldAllowRequest - ? Ci.nsIContentAnalysisResponse.eAllow - : Ci.nsIContentAnalysisResponse.eBlock; - }, - - // nsIContentAnalysis methods - async analyzeContentRequest(request, _autoAcknowledge) { - info( - "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" + - this.shouldAllowRequest + - ", this.errorValue=" + - this.errorValue - ); - this.calls.push(request); - if (this.errorValue) { - throw this.errorValue; - } - // Use setTimeout to simulate an async activity - await new Promise(res => setTimeout(res, 0)); - return makeContentAnalysisResponse(this.getAction(), request.requestToken); - }, - - analyzeContentRequestCallback(request, autoAcknowledge, callback) { - info( - "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" + - this.shouldAllowRequest + - ", this.errorValue=" + - this.errorValue - ); - this.calls.push(request); - if (this.errorValue) { - throw this.errorValue; - } - let response = makeContentAnalysisResponse( - this.getAction(), - request.requestToken - ); - // Use setTimeout to simulate an async activity - setTimeout(() => { - callback.contentResult(response); - }, 0); - }, -}; +let mockCA = makeMockContentAnalysis(); add_setup(async function test_setup() { mockCA = mockContentAnalysisService(mockCA); diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html new file mode 100644 index 0000000000..9604633842 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html @@ -0,0 +1,61 @@ +<html><body> +<script> +async function checkPaste(event) { + let result = null; + try { + result = await checkPasteHelper(event); + } catch (e) { + result = e.toString(); + } + + document.dispatchEvent(new CustomEvent('testresult', { + detail: { result } + })); +} + +function is(a, b, msg) { + if (!Object.is(a, b)) { + throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`); + } +} + +async function checkPasteHelper(event) { + let dt = event.clipboardData; + // Set by injected JS in the test + let filePasteAllowed = document.getElementById("pasteAllowed").checked; + + is(dt.types.length, 2, "Correct number of types"); + + // TODO: Remove application/x-moz-file from content. + is(dt.types[0], "application/x-moz-file", "First type") + is(dt.types[1], "Files", "Last type must be Files"); + + is(dt.getData("text/plain"), "", "text/plain found with getData"); + is(dt.getData("application/x-moz-file"), "", "application/x-moz-file found with getData"); + + if (!filePasteAllowed) { + is(dt.files.length, 0, "No files"); + } else { + is(dt.files.length, 1, "Correct number of files"); + is(dt.files[0].type, "text/plain", "Correct file type"); + } + is(dt.items.length, 1, "Correct number of items"); + is(dt.items[0].kind, "file", "Correct item kind"); + if (!filePasteAllowed) { + is(dt.items[0].type, "application/x-moz-file", "Correct item type"); + return ""; + } + is(dt.items[0].type, "text/plain", "Correct item type"); + + let file = dt.files[0]; + is(await file.text(), "Hello World!", "Pasted file contains right text"); + + return file.name; +} +</script> + +<input id="input" onpaste="checkPaste(event)"> + +<label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox"> + +</body></html> diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html new file mode 100644 index 0000000000..db93e85955 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html @@ -0,0 +1,41 @@ +<html> +<body> + +<div id="content"> + <input + id="testInput" + type="text" onpaste="handlePaste(event)"> + <textarea id="testTextArea" onpaste="handlePaste(event)"></textarea> + + <label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox"> +</div> +<script class="testbody" type="application/javascript"> +function is(a, b, msg) { + if (!Object.is(a, b)) { + throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`); + } +} + +function checkPasteHelper(event) { + // Set by injected JS in the test + let filePasteAllowed = document.getElementById("pasteAllowed").checked; + is(event.clipboardData.getData('text/plain'), filePasteAllowed ? "Just some text" : "", "getData(text/plain) should return plain text"); + is(event.clipboardData.types.length, 1, "Correct number of types"); +} + +function handlePaste(e) { + let result = null; + try { + result = checkPasteHelper(e); + } catch (e) { + result = e.toString(); + } + + document.dispatchEvent(new CustomEvent('testresult', { + detail: { result } + })); +} +</script> + +</body> +</html> diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html new file mode 100644 index 0000000000..eefc40de85 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html @@ -0,0 +1,46 @@ +<html> +<body> + +<script class="testbody" type="application/javascript"> +function is(a, b, msg) { + if (!Object.is(a, b)) { + throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`); + } +} + +function checkPasteHelper(event) { + is(event.clipboardData.types.indexOf('text/html'), -1, "clipboardData shouldn't have text/html"); + // Set by injected JS in the test + let filePasteAllowed = document.getElementById("pasteAllowed").checked; + is(event.clipboardData.getData('text/plain'), filePasteAllowed ? "Some text" : "", "getData(text/plain) should return plain text"); + return true; +} + +window.addEventListener("paste", e => { + let result = null; + try { + result = checkPasteHelper(e); + } catch (e) { + result = e.toString(); + } + + document.dispatchEvent(new CustomEvent('testresult', { + detail: { result } + })); +}); + +document.addEventListener("teststart", _e => { + let editable = document.getElementById("editable1"); + editable.focus(); + + window.getSelection().selectAllChildren(editable); + + SpecialPowers.doCommand(window, "cmd_pasteNoFormatting"); +}); +</script> + +<div contenteditable="true" id="editable1"><b>Formatted Text</b><br></div> + +<label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox"> +</body> +</html> diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html new file mode 100644 index 0000000000..8017cc87a1 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html @@ -0,0 +1,7 @@ +<html> +<body> + +<div id="content"></div> + +</body> +</html> diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html b/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html new file mode 100644 index 0000000000..14414a0f28 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html @@ -0,0 +1,95 @@ +<html> +<body> + +<!--<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>--> +<script class="testbody" type="application/javascript"> +function is(a, b, msg) { + if (!Object.is(a, b)) { + throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`); + } +} + +function isNotAllowedException(ex) { + return /NS_ERROR_CONTENT_BLOCKED/.test(ex.toString()) || + /DataError/.test(ex.toString()) || + /NotAllowedError/.test(ex.toString()); +} + +async function checkReadHelper(readResult, gotNotAllowedException, isReadTextTest) { + // Set by injected JS in the test + let filePasteAllowed = document.getElementById("pasteAllowed").checked; + let contentAnalysisReturnsError = document.getElementById("contentAnalysisReturnsError").checked; + filePasteAllowed = filePasteAllowed && !contentAnalysisReturnsError; + is(gotNotAllowedException, !filePasteAllowed && isReadTextTest, "Should get exception from readText() if not allowed"); + if (isReadTextTest) { + is(readResult, filePasteAllowed ? "Some plain text" : null, "Should get expected text from clipboard.readText()"); + } + else { + is(readResult.length, 1, "check number of ClipboardItems in response"); + is(readResult[0].types.length, 2, "check number of types in ClipboardItem"); + + { + let text = null; + let gotNotAllowedException = false; + try { + let textBlob = await readResult[0].getType("text/plain"); + text = await textBlob.text(); + } catch (ex) { + gotNotAllowedException = isNotAllowedException(ex); + } + is(gotNotAllowedException, !filePasteAllowed, "should get exception from reading text data when blocked"); + if (filePasteAllowed) { + is(text, "Some plain text", "check text/plain data"); + } + } + + { + let html = null; + let gotNotAllowedException = false; + try { + let htmlBlob = await readResult[0].getType("text/html"); + html = await htmlBlob.text(); + } catch (ex) { + gotNotAllowedException = isNotAllowedException(ex); + } + is(gotNotAllowedException, !filePasteAllowed, "should get exception from reading html data when blocked"); + if (filePasteAllowed) { + const CLIPBOARD_HTML_STRING = "<b>Some HTML</b>"; + let expectedHtml = navigator.platform.includes("Win") + ? `<html><body>\n<!--StartFragment-->${CLIPBOARD_HTML_STRING}<!--EndFragment-->\n</body>\n</html>` + : CLIPBOARD_HTML_STRING; + is(html, expectedHtml, "check text/html data"); + } + } + } + return true; +} + +document.addEventListener("teststart", async e => { + let isReadTextTest = e.detail.testType == "readText"; + let gotNotAllowedException = false; + let readResult = null; + try { + let readPromise = isReadTextTest ? navigator.clipboard.readText() : navigator.clipboard.read(); + readResult = await readPromise; + } catch (ex) { + gotNotAllowedException = isNotAllowedException(ex); + } + + let result = null; + try { + result = checkReadHelper(readResult, gotNotAllowedException, isReadTextTest); + } catch (ex) { + result = ex.toString(); + } + + document.dispatchEvent(new CustomEvent('testresult', { + detail: { result } + })); +}); +</script> + +<label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox"> +<label for="contentAnalysisReturnsError">Content Analysis returns error?</label><input id="contentAnalysisReturnsError" type="checkbox"> +</body> +</html> diff --git a/toolkit/components/contentanalysis/tests/browser/head.js b/toolkit/components/contentanalysis/tests/browser/head.js index e645caa2d7..9422a62ff2 100644 --- a/toolkit/components/contentanalysis/tests/browser/head.js +++ b/toolkit/components/contentanalysis/tests/browser/head.js @@ -112,3 +112,127 @@ async function waitForFileToAlmostMatchSize(filePath, expectedSize) { return Math.abs(fileStat.size - expectedSize) <= maxSizeDifference; }, "Sizes should (almost) match"); } + +function makeMockContentAnalysis() { + return { + isActive: true, + mightBeActive: true, + errorValue: undefined, + + setupForTest(shouldAllowRequest) { + this.shouldAllowRequest = shouldAllowRequest; + this.errorValue = undefined; + this.calls = []; + }, + + setupForTestWithError(errorValue) { + this.errorValue = errorValue; + this.calls = []; + }, + + clearCalls() { + this.calls = []; + }, + + getAction() { + if (this.shouldAllowRequest === undefined) { + this.shouldAllowRequest = true; + } + return this.shouldAllowRequest + ? Ci.nsIContentAnalysisResponse.eAllow + : Ci.nsIContentAnalysisResponse.eBlock; + }, + + // nsIContentAnalysis methods + async analyzeContentRequest(request, _autoAcknowledge) { + info( + "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" + + this.shouldAllowRequest + + ", this.errorValue=" + + this.errorValue + ); + this.calls.push(request); + if (this.errorValue) { + throw this.errorValue; + } + // Use setTimeout to simulate an async activity + await new Promise(res => setTimeout(res, 0)); + return makeContentAnalysisResponse( + this.getAction(), + request.requestToken + ); + }, + + analyzeContentRequestCallback(request, autoAcknowledge, callback) { + info( + "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" + + this.shouldAllowRequest + + ", this.errorValue=" + + this.errorValue + ); + this.calls.push(request); + if (this.errorValue) { + throw this.errorValue; + } + let response = makeContentAnalysisResponse( + this.getAction(), + request.requestToken + ); + // Use setTimeout to simulate an async activity + setTimeout(() => { + callback.contentResult(response); + }, 0); + }, + }; +} + +function whenTabLoaded(aTab, aCallback) { + promiseTabLoadEvent(aTab).then(aCallback); +} + +function promiseTabLoaded(aTab) { + return new Promise(resolve => { + whenTabLoaded(aTab, resolve); + }); +} + +/** + * Waits for a load (or custom) event to finish in a given tab. If provided + * load an uri into the tab. + * + * @param {object} tab + * The tab to load into. + * @param {string} [url] + * The url to load, or the current url. + * @returns {Promise<string>} resolved when the event is handled. Rejected if + * a valid load event is not received within a meaningful interval + */ +function promiseTabLoadEvent(tab, url) { + info("Wait tab event: load"); + + function handle(loadedUrl) { + if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { + info(`Skipping spurious load event for ${loadedUrl}`); + return false; + } + + info("Tab event received: load"); + return true; + } + + let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); + + if (url) { + BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url); + } + + return loaded; +} + +function promisePopupShown(popup) { + return BrowserTestUtils.waitForPopupEvent(popup, "shown"); +} + +function promisePopupHidden(popup) { + return BrowserTestUtils.waitForPopupEvent(popup, "hidden"); +} |