diff options
Diffstat (limited to 'toolkit/components/contentanalysis/tests/browser')
6 files changed, 903 insertions, 3 deletions
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, @@ -103,6 +113,18 @@ add_task(async function test_ca_enterprise_config() { "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, "per user match" @@ -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 @@ +<!doctype html> +<p>Some random text</p> +<button onclick="print()">Print the page</button> +<pre id="log"></pre> +<script> +let i = 0; +for (let t of ["beforeprint", "afterprint"]) { + addEventListener(t, () => { + document.getElementById("log").appendChild(document.createTextNode(`[${i++}] ${t}\n`)); + }); +} +</script> 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"); +} |