/* 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 = "" + " " + "
Test Bold After Text
" + ""; let mockCA = makeMockContentAnalysis(); add_setup(async function test_setup() { mockCA = await mockContentAnalysisService(mockCA); }); function setClipboardHTMLData(htmlString) { const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( Ci.nsITransferable ); trans.init(null); trans.addDataFlavor("text/html"); const str = Cc["@mozilla.org/supports-string;1"].createInstance( Ci.nsISupportsString ); str.data = htmlString; trans.setTransferData("text/html", str); // Write to clipboard. Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard); } async function testClipboardWithContentAnalysis(allowPaste, plainTextOnly) { 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") ? "\n" : ""; const htmlPostfix = navigator.platform.includes("Win") ? "\n\n" : ""; 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, allowPaste ? 1 : 0, "Items on clipboard" ); Assert.equal( clipboardData.types.length, allowPaste ? 2 : 0, "Types on clipboard" ); if (allowPaste) { 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 Bold" + 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; // Check that the number of calls matches the number of // kKnownClipboardTypes on the clipboard. is( mockCA.calls.length, plainTextOnly ? 1 : 2, "Correct number of calls to Content Analysis" ); assertContentAnalysisRequest( mockCA.calls[0], "t Bold", mockCA.calls[0].userActionId, plainTextOnly ? 1 : 2 ); if (!plainTextOnly) { assertContentAnalysisRequest( mockCA.calls[1], htmlPrefix + "t Bold" + htmlPostfix, mockCA.calls[0].userActionId, 2 ); is( mockCA.agentCancelCalls, allowPaste ? 0 : 1, "Response cancels other requests only if it is BLOCK" ); } mockCA.clearCalls(); let copyPromise = SpecialPowers.spawn(browser, [allowPaste], allowPaste => { var main = content.document.getElementById("main"); Assert.equal( main.innerHTML, allowPaste ? "Test Bold After Textt Bold" : "Test Bold After Text", "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", "Italic "); 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, allowPaste ? 1 : 0, "Items on clipboard 2" ); Assert.equal( clipboardData.types.length, allowPaste ? 2 : 0, "Types on clipboard 2" ); if (allowPaste) { 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 + "Italic " + 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, plainTextOnly ? 1 : 2, "Correct number of calls to Content Analysis" ); assertContentAnalysisRequest( mockCA.calls[0], "Some text", mockCA.calls[0].userActionId, plainTextOnly ? 1 : 2 ); if (!plainTextOnly) { assertContentAnalysisRequest( mockCA.calls[1], htmlPrefix + "Italic " + htmlPostfix, mockCA.calls[0].userActionId, 2 ); } mockCA.clearCalls(); await SpecialPowers.spawn(browser, [allowPaste], allowPaste => { var main = content.document.getElementById("main"); Assert.equal( main.innerHTML, allowPaste ? "Italic Test Bold After" : "Test Bold", "Copy and paste html 2" ); }); // Next, put some HTML data on the clipboard setClipboardHTMLData( '' ); // Focus the content again await SimpleTest.promiseFocus(browser); pastePromise = SpecialPowers.spawn( browser, [htmlPrefix, htmlPostfix, allowPaste, plainTextOnly], (htmlPrefixChild, htmlPostfixChild, allowPaste, plainTextOnly) => { 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 || plainTextOnly) { if ( clipboardText !== htmlPrefixChild + '' + 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, plainTextOnly ? 0 : 1, "Correct number of calls to Content Analysis" ); if (!plainTextOnly) { assertContentAnalysisRequest( mockCA.calls[0], htmlPrefix + '' + htmlPostfix, mockCA.calls[0].userActionId, 1 ); } mockCA.clearCalls(); // The new content should now include an image. await SpecialPowers.spawn( browser, [allowPaste, plainTextOnly], (allowPaste, plainTextOnly) => { var main = content.document.getElementById("main"); let expectedContents; if (allowPaste) { expectedContents = 'Italic ' + "Test Bold After"; } else { // If plainTextOnly then no CA call will have been made, so // the content will be allowed. (but the earlier "Italic" part was not) expectedContents = plainTextOnly ? '' + "Test Bold" : "Test Bold"; } Assert.equal(main.innerHTML, expectedContents, "Paste after copy image"); } ); gBrowser.removeCurrentTab(); } function assertContentAnalysisRequest( request, expectedText, expectedUserActionId, expectedRequestsCount ) { // This page is loaded via a data: URL which has a null principal, // so the URL will reflect this. ok( request.url.spec.startsWith("moz-nullprincipal:"), "request has correct moz-nullprincipal URL, got " + request.url.spec ); is( request.analysisType, Ci.nsIContentAnalysisRequest.eBulkDataEntry, "request has correct analysisType" ); is( request.reason, Ci.nsIContentAnalysisRequest.eClipboardPaste, "request has correct reason" ); is( request.operationTypeForDisplay, Ci.nsIContentAnalysisRequest.eClipboard, "request has correct operationTypeForDisplay" ); is(request.filePath, "", "request filePath should be empty"); if (expectedText !== null) { is(request.textContent, expectedText, "request textContent should match"); } is( request.userActionRequestsCount, expectedRequestsCount, "request userActionRequestsCount should match" ); is( request.userActionId, expectedUserActionId, "request userActionId should match" ); ok(request.userActionId.length, "request userActionId should not be empty"); 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 testClipboardWithContentAnalysisCheckPlainTextOnly() { await SpecialPowers.pushPrefEnv({ set: [ [ "browser.contentanalysis.interception_point.clipboard.plain_text_only", true, ], ], }); await testClipboardWithContentAnalysis(true, true); await SpecialPowers.popPrefEnv(); }); add_task(async function testClipboardWithContentAnalysisCheckAllFormats() { await SpecialPowers.pushPrefEnv({ set: [ [ "browser.contentanalysis.interception_point.clipboard.plain_text_only", false, ], ], }); await testClipboardWithContentAnalysis(true, false); await SpecialPowers.popPrefEnv(); }); add_task( async function testClipboardWithContentAnalysisBlockCheckPlainTextOnly() { await SpecialPowers.pushPrefEnv({ set: [ [ "browser.contentanalysis.interception_point.clipboard.plain_text_only", true, ], ], }); await testClipboardWithContentAnalysis(false, true); await SpecialPowers.popPrefEnv(); } ); add_task(async function testClipboardWithContentAnalysisBlockCheckAllFormats() { await SpecialPowers.pushPrefEnv({ set: [ [ "browser.contentanalysis.interception_point.clipboard.plain_text_only", false, ], ], }); await testClipboardWithContentAnalysis(false, false); await SpecialPowers.popPrefEnv(); });