From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../contentanalysis/tests/browser/browser.toml | 17 + .../browser/browser_content_analysis_policies.js | 34 +- ...browser_print_changing_page_content_analysis.js | 339 ++++++++++++++++++ .../browser/browser_print_content_analysis.js | 390 +++++++++++++++++++++ .../tests/browser/changing_page_for_print.html | 12 + .../contentanalysis/tests/browser/head.js | 114 ++++++ 6 files changed, 903 insertions(+), 3 deletions(-) create mode 100644 toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js create mode 100644 toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js create mode 100644 toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html create mode 100644 toolkit/components/contentanalysis/tests/browser/head.js (limited to 'toolkit/components/contentanalysis/tests') diff --git a/toolkit/components/contentanalysis/tests/browser/browser.toml b/toolkit/components/contentanalysis/tests/browser/browser.toml index 0e21090299..bdbf350593 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser.toml +++ b/toolkit/components/contentanalysis/tests/browser/browser.toml @@ -1,3 +1,20 @@ [DEFAULT] +run-if = ["os == 'win'"] +support-files = [ + "head.js", +] ["browser_content_analysis_policies.js"] + +["browser_print_changing_page_content_analysis.js"] +support-files = [ + "!/toolkit/components/printing/tests/head.js", + "changing_page_for_print.html", +] + +["browser_print_content_analysis.js"] +support-files = [ + "!/toolkit/components/printing/tests/head.js", + "!/toolkit/components/printing/tests/longerArticle.html", + "!/toolkit/components/printing/tests/simplifyArticleSample.html", +] 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 e2e001e9d1..b226c0a37a 100644 --- a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js +++ b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js @@ -11,15 +11,18 @@ "use strict"; -const { EnterprisePolicyTesting } = ChromeUtils.importESModule( - "resource://testing-common/EnterprisePolicyTesting.sys.mjs" -); +const { EnterprisePolicyTesting, PoliciesPrefTracker } = + ChromeUtils.importESModule( + "resource://testing-common/EnterprisePolicyTesting.sys.mjs" + ); const kEnabledPref = "enabled"; const kPipeNamePref = "pipe_path_name"; const kTimeoutPref = "agent_timeout"; const kAllowUrlPref = "allow_url_regex_list"; const kDenyUrlPref = "deny_url_regex_list"; +const kAgentNamePref = "agent_name"; +const kClientSignaturePref = "client_signature"; const kPerUserPref = "is_per_user"; const kShowBlockedPref = "show_blocked_result"; const kDefaultAllowPref = "default_allow"; @@ -29,6 +32,7 @@ const ca = Cc["@mozilla.org/contentanalysis;1"].getService( ); add_task(async function test_ca_active() { + PoliciesPrefTracker.start(); ok(!ca.isActive, "CA is inactive when pref and cmd line arg are missing"); // Set the pref without enterprise policy. CA should not be active. @@ -62,11 +66,15 @@ add_task(async function test_ca_active() { }, }); ok(ca.isActive, "CA is active when enabled by enterprise policy pref"); + PoliciesPrefTracker.stop(); }); add_task(async function test_ca_enterprise_config() { + PoliciesPrefTracker.start(); const string1 = "this is a string"; const string2 = "this is another string"; + const string3 = "an agent name"; + const string4 = "a client signature"; await EnterprisePolicyTesting.setupPolicyEngineWithJson({ policies: { @@ -75,6 +83,8 @@ add_task(async function test_ca_enterprise_config() { AgentTimeout: 99, AllowUrlRegexList: string1, DenyUrlRegexList: string2, + AgentName: string3, + ClientSignature: string4, IsPerUser: true, ShowBlockedResult: false, DefaultAllow: true, @@ -102,6 +112,18 @@ add_task(async function test_ca_enterprise_config() { string2, "deny urls match" ); + is( + Services.prefs.getStringPref("browser.contentanalysis." + kAgentNamePref), + string3, + "agent names match" + ); + is( + Services.prefs.getStringPref( + "browser.contentanalysis." + kClientSignaturePref + ), + string4, + "client signatures match" + ); is( Services.prefs.getBoolPref("browser.contentanalysis." + kPerUserPref), true, @@ -117,6 +139,7 @@ add_task(async function test_ca_enterprise_config() { true, "default allow match" ); + PoliciesPrefTracker.stop(); }); add_task(async function test_cleanup() { @@ -124,4 +147,9 @@ add_task(async function test_cleanup() { await EnterprisePolicyTesting.setupPolicyEngineWithJson({ policies: {}, }); + // These may have gotten set when ContentAnalysis was enabled through + // the policy and do not get cleared if there is no ContentAnalysis + // element - reset them manually here. + ca.isSetByEnterprisePolicy = false; + Services.prefs.setBoolPref("browser.contentanalysis." + kEnabledPref, false); }); 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 new file mode 100644 index 0000000000..72a7dcbb91 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js @@ -0,0 +1,339 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js", + this +); + +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); + }, +}; + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const TEST_PAGE_URL = PrintHelper.getTestPageUrlHTTPS( + "changing_page_for_print.html" +); + +function addUniqueSuffix(prefix) { + return `${prefix}-${Services.uuid + .generateUUID() + .toString() + .slice(1, -1)}.pdf`; +} + +async function printToDestination(aBrowser, aDestination) { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`); + let filePath = PathUtils.join(tmpDir.path, fileName); + + info(`Printing to ${filePath}`); + + let settings = PSSVC.createNewPrintSettings(); + settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; + settings.outputDestination = aDestination; + + settings.headerStrCenter = ""; + settings.headerStrLeft = ""; + settings.headerStrRight = ""; + settings.footerStrCenter = ""; + settings.footerStrLeft = ""; + settings.footerStrRight = ""; + + settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */ + let outStream = null; + if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) { + settings.toFileName = PathUtils.join(tmpDir.path, fileName); + } else { + is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream); + outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let tmpFile = tmpDir.clone(); + tmpFile.append(fileName); + outStream.init(tmpFile, -1, 0o666, 0); + settings.outputStream = outStream; + } + + await aBrowser.browsingContext.print(settings); + + return filePath; +} + +function assertContentAnalysisRequest(request) { + is(request.url.spec, TEST_PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.ePrint, + "request has print analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eOperationPrint, + "request has print operationTypeForDisplay" + ); + is(request.textContent, "", "request textContent should be empty"); + is(request.filePath, "", "request filePath should be empty"); + isnot(request.printDataHandle, 0, "request printDataHandle should not be 0"); + isnot(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndAllowing() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + + // This effectively tests that the PDF content sent to Content Analysis + // and the content that is actually printed matches. This is necessary + // because a previous iteration of the Content Analysis code didn't use + // a static Document clone for this and so the content would differ. (since + // the .html file in question adds content to the page when print events + // happen) + await waitForFileToAlmostMatchSize( + filePath, + mockCA.calls[0].printDataSize + ); + + await IOUtils.remove(filePath); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_CONTENT_BLOCKED/.test(e.toString()), + "Got content blocked error" + ); + } + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task(async function testPrintToStreamWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_NOT_AVAILABLE/.test(e.toString()), + "Error in mock CA was propagated out" + ); + } + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task(async function testPrintThroughDialogWithContentAnalysisActive() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + await helper.startPrint(); + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + + await waitForFileToAlmostMatchSize( + file.path, + mockCA.calls[0].printDataSize + ); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task( + async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintThroughDialogWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js new file mode 100644 index 0000000000..9b4c0ffa60 --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js @@ -0,0 +1,390 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js", + this +); + +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); + }, +}; + +add_setup(async function test_setup() { + mockCA = mockContentAnalysisService(mockCA); +}); + +const TEST_PAGE_URL = + "https://example.com/browser/toolkit/components/printing/tests/simplifyArticleSample.html"; +const TEST_PAGE_URL_2 = + "https://example.com/browser/toolkit/components/printing/tests/longerArticle.html"; + +function addUniqueSuffix(prefix) { + return `${prefix}-${Services.uuid + .generateUUID() + .toString() + .slice(1, -1)}.pdf`; +} + +async function printToDestination(aBrowser, aDestination) { + let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`); + let filePath = PathUtils.join(tmpDir.path, fileName); + + info(`Printing to ${filePath}`); + + let settings = PSSVC.createNewPrintSettings(); + settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF; + settings.outputDestination = aDestination; + + settings.headerStrCenter = ""; + settings.headerStrLeft = ""; + settings.headerStrRight = ""; + settings.footerStrCenter = ""; + settings.footerStrLeft = ""; + settings.footerStrRight = ""; + + settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */ + let outStream = null; + if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) { + settings.toFileName = PathUtils.join(tmpDir.path, fileName); + } else { + is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream); + outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let tmpFile = tmpDir.clone(); + tmpFile.append(fileName); + outStream.init(tmpFile, -1, 0o666, 0); + settings.outputStream = outStream; + } + + await aBrowser.browsingContext.print(settings); + + return filePath; +} + +function assertContentAnalysisRequest(request, expectedUrl) { + is(request.url.spec, expectedUrl ?? TEST_PAGE_URL, "request has correct URL"); + is( + request.analysisType, + Ci.nsIContentAnalysisRequest.ePrint, + "request has print analysisType" + ); + is( + request.operationTypeForDisplay, + Ci.nsIContentAnalysisRequest.eOperationPrint, + "request has print operationTypeForDisplay" + ); + is(request.textContent, "", "request textContent should be empty"); + is(request.filePath, "", "request filePath should be empty"); + isnot(request.printDataHandle, 0, "request printDataHandle should not be 0"); + isnot(request.printDataSize, 0, "request printDataSize should not be 0"); + ok(!!request.requestToken.length, "request requestToken should not be empty"); +} + +// Printing to a stream is different than going through the print preview dialog because it +// doesn't make a static clone of the document before the print, which causes the +// Content Analysis code to go through a different code path. This is similar to what +// happens when various preferences are set to skip the print preview dialog, for example +// print.prefer_system_dialog. +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndAllowing() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + + await waitForFileToAlmostMatchSize( + filePath, + mockCA.calls[0].printDataSize + ); + + await IOUtils.remove(filePath); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintToStreamAfterNavigationWithContentAnalysisActiveAndAllowing() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + let filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + mockCA.clearCalls(); + + await IOUtils.remove(filePath); + + BrowserTestUtils.startLoadingURIString( + helper.sourceBrowser, + TEST_PAGE_URL_2 + ); + await BrowserTestUtils.browserLoaded(helper.sourceBrowser); + + filePath = await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0], TEST_PAGE_URL_2); + await waitForFileToAlmostMatchSize( + filePath, + mockCA.calls[0].printDataSize + ); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintToStreamWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_CONTENT_BLOCKED/.test(e.toString()), + "Got content blocked error" + ); + } + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task(async function testPrintToStreamWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + try { + await printToDestination( + helper.sourceBrowser, + Ci.nsIPrintSettings.kOutputDestinationFile + ); + ok(false, "Content analysis should make this fail to print"); + } catch (e) { + ok( + /NS_ERROR_NOT_AVAILABLE/.test(e.toString()), + "Error in mock CA was propagated out" + ); + } + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task(async function testPrintThroughDialogWithContentAnalysisActive() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(true); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + + is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis"); + assertContentAnalysisRequest(mockCA.calls[0]); + + await waitForFileToAlmostMatchSize( + file.path, + mockCA.calls[0].printDataSize + ); + }, + TEST_PAGE_URL, + true + ); +}); + +add_task( + async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() { + await PrintHelper.withTestPage( + async helper => { + mockCA.setupForTest(false); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); + +add_task( + async function testPrintThroughDialogWithContentAnalysisReturningError() { + await PrintHelper.withTestPage( + async helper => { + expectUncaughtException(); + mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE); + + await helper.startPrint(); + let fileName = addUniqueSuffix(`printDialogTest`); + let file = helper.mockFilePicker(fileName); + info(`Printing to ${file.path}`); + try { + await helper.assertPrintToFile(file, () => { + EventUtils.sendKey("return", helper.win); + }); + } catch (e) { + ok( + /Wait for target file to get created/.test(e.toString()), + "Target file should not get created" + ); + } + ok(!file.exists(), "File should not exist"); + + is( + mockCA.calls.length, + 1, + "Correct number of calls to Content Analysis" + ); + assertContentAnalysisRequest(mockCA.calls[0]); + }, + TEST_PAGE_URL, + true + ); + } +); diff --git a/toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html b/toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html new file mode 100644 index 0000000000..de6f9001aa --- /dev/null +++ b/toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html @@ -0,0 +1,12 @@ + +

Some random text

+ +

+
diff --git a/toolkit/components/contentanalysis/tests/browser/head.js b/toolkit/components/contentanalysis/tests/browser/head.js
new file mode 100644
index 0000000000..e645caa2d7
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/head.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { MockRegistrar } = ChromeUtils.importESModule(
+  "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+// Wraps the given object in an XPConnect wrapper and, if an interface
+// is passed, queries the result to that interface.
+function xpcWrap(obj, iface) {
+  let ifacePointer = Cc[
+    "@mozilla.org/supports-interface-pointer;1"
+  ].createInstance(Ci.nsISupportsInterfacePointer);
+
+  ifacePointer.data = obj;
+  if (iface) {
+    return ifacePointer.data.QueryInterface(iface);
+  }
+  return ifacePointer.data;
+}
+
+/**
+ * Mock a (set of) service(s) as the object mockService.
+ *
+ * @param {[string]} serviceNames
+ *                   array of services names that mockService will be
+ *                   allowed to QI to.  Must include the name of the
+ *                   service referenced by contractId.
+ * @param {string}   contractId
+ *                   the component ID that will reference the mock object
+ *                   instead of the original service
+ * @param {object}   interfaceObj
+ *                   interface object for the component
+ * @param {object}   mockService
+ *                   object that satisfies the contract well
+ *                   enough to use as a mock of it
+ * @returns {object} The newly-mocked service
+ */
+function mockService(serviceNames, contractId, interfaceObj, mockService) {
+  // xpcWrap allows us to mock [implicit_jscontext] methods.
+  let newService = {
+    ...mockService,
+    QueryInterface: ChromeUtils.generateQI(serviceNames),
+  };
+  let o = xpcWrap(newService, interfaceObj);
+  let cid = MockRegistrar.register(contractId, o);
+  registerCleanupFunction(() => {
+    MockRegistrar.unregister(cid);
+  });
+  return newService;
+}
+
+/**
+ * Mock the nsIContentAnalysis service with the object mockCAService.
+ *
+ * @param {object}    mockCAService
+ *                    the service to mock for nsIContentAnalysis
+ * @returns {object}  The newly-mocked service
+ */
+function mockContentAnalysisService(mockCAService) {
+  return mockService(
+    ["nsIContentAnalysis"],
+    "@mozilla.org/contentanalysis;1",
+    Ci.nsIContentAnalysis,
+    mockCAService
+  );
+}
+
+/**
+ * Make an nsIContentAnalysisResponse.
+ *
+ * @param {number} action The action to take, from the
+ *  nsIContentAnalysisResponse.Action enum.
+ * @param {string} token The requestToken.
+ * @returns {object} An object that conforms to nsIContentAnalysisResponse.
+ */
+function makeContentAnalysisResponse(action, token) {
+  return {
+    action,
+    shouldAllowContent: action != Ci.nsIContentAnalysisResponse.eBlock,
+    requestToken: token,
+    acknowledge: _acknowledgement => {},
+  };
+}
+
+async function waitForFileToAlmostMatchSize(filePath, expectedSize) {
+  // In Cocoa the CGContext adds a hash, plus there are other minor
+  // non-user-visible differences, so we need to be a bit more sloppy there.
+  //
+  // We see one byte difference in Windows and Linux on automation sometimes,
+  // though files are consistently the same locally, that needs
+  // investigation, but it's probably harmless.
+  // Note that this is copied from browser_print_stream.js.
+  const maxSizeDifference = AppConstants.platform == "macosx" ? 100 : 3;
+
+  // Buffering shenanigans? Wait for sizes to match... There's no great
+  // IOUtils methods to force a flush without writing anything...
+  // Note that this means if this results in a timeout this is exactly
+  // the same as a test failure.
+  // This is taken from toolkit/components/printing/tests/browser_print_stream.js
+  await TestUtils.waitForCondition(async function () {
+    let fileStat = await IOUtils.stat(filePath);
+
+    info("got size: " + fileStat.size + " expected: " + expectedSize);
+    Assert.greater(
+      fileStat.size,
+      0,
+      "File should not be empty: " + fileStat.size
+    );
+    return Math.abs(fileStat.size - expectedSize) <= maxSizeDifference;
+  }, "Sizes should (almost) match");
+}
-- 
cgit v1.2.3