summaryrefslogtreecommitdiffstats
path: root/toolkit/components/contentanalysis
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/contentanalysis')
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.cpp466
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.h93
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h29
-rw-r--r--toolkit/components/contentanalysis/components.conf1
-rw-r--r--toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc187
-rw-r--r--toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h200
-rw-r--r--toolkit/components/contentanalysis/nsIContentAnalysis.idl54
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser.toml17
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js34
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js339
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js390
-rw-r--r--toolkit/components/contentanalysis/tests/browser/changing_page_for_print.html12
-rw-r--r--toolkit/components/contentanalysis/tests/browser/head.js114
13 files changed, 1799 insertions, 137 deletions
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp
index 1c71f5d986..2977072984 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.cpp
+++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp
@@ -11,6 +11,7 @@
#include "base/process_util.h"
#include "GMPUtils.h" // ToHexString
#include "mozilla/Components.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/Logging.h"
@@ -24,6 +25,9 @@
#include "nsIFile.h"
#include "nsIGlobalObject.h"
#include "nsIObserverService.h"
+#include "nsIOutputStream.h"
+#include "nsIPrintSettings.h"
+#include "nsIStorageStream.h"
#include "ScopedNSSTypes.h"
#include "xpcpublic.h"
@@ -35,6 +39,7 @@
# include <windows.h>
# define SECURITY_WIN32 1
# include <security.h>
+# include "mozilla/NativeNt.h"
# include "mozilla/WinDllServices.h"
#endif // XP_WIN
@@ -114,6 +119,11 @@ nsIContentAnalysisAcknowledgement::FinalAction ConvertResult(
} // anonymous namespace
namespace mozilla::contentanalysis {
+ContentAnalysisRequest::~ContentAnalysisRequest() {
+#ifdef XP_WIN
+ CloseHandle(mPrintDataHandle);
+#endif
+}
NS_IMETHODIMP
ContentAnalysisRequest::GetAnalysisType(AnalysisType* aAnalysisType) {
@@ -134,6 +144,34 @@ ContentAnalysisRequest::GetFilePath(nsAString& aFilePath) {
}
NS_IMETHODIMP
+ContentAnalysisRequest::GetPrintDataHandle(uint64_t* aPrintDataHandle) {
+#ifdef XP_WIN
+ uintptr_t printDataHandle = reinterpret_cast<uintptr_t>(mPrintDataHandle);
+ uint64_t printDataValue = static_cast<uint64_t>(printDataHandle);
+ *aPrintDataHandle = printDataValue;
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
+ContentAnalysisRequest::GetPrinterName(nsAString& aPrinterName) {
+ aPrinterName = mPrinterName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentAnalysisRequest::GetPrintDataSize(uint64_t* aPrintDataSize) {
+#ifdef XP_WIN
+ *aPrintDataSize = mPrintDataSize;
+ return NS_OK;
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
+}
+
+NS_IMETHODIMP
ContentAnalysisRequest::GetUrl(nsIURI** aUrl) {
NS_ENSURE_ARG_POINTER(aUrl);
NS_IF_ADDREF(*aUrl = mUrl);
@@ -234,6 +272,8 @@ ContentAnalysisRequest::ContentAnalysisRequest(
mUrl(std::move(aUrl)),
mSha256Digest(std::move(aSha256Digest)),
mWindowGlobalParent(aWindowGlobalParent) {
+ MOZ_ASSERT(aAnalysisType != AnalysisType::ePrint,
+ "Print should use other ContentAnalysisRequest constructor!");
if (aStringIsFilePath) {
mFilePath = std::move(aString);
} else {
@@ -251,6 +291,32 @@ ContentAnalysisRequest::ContentAnalysisRequest(
mRequestToken = GenerateRequestToken();
}
+ContentAnalysisRequest::ContentAnalysisRequest(
+ const nsTArray<uint8_t> aPrintData, nsCOMPtr<nsIURI> aUrl,
+ nsString aPrinterName, dom::WindowGlobalParent* aWindowGlobalParent)
+ : mAnalysisType(AnalysisType::ePrint),
+ mUrl(std::move(aUrl)),
+ mPrinterName(std::move(aPrinterName)),
+ mWindowGlobalParent(aWindowGlobalParent) {
+#ifdef XP_WIN
+ LARGE_INTEGER dataContentLength;
+ dataContentLength.QuadPart = static_cast<LONGLONG>(aPrintData.Length());
+ mPrintDataHandle = ::CreateFileMappingW(
+ INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, dataContentLength.HighPart,
+ dataContentLength.LowPart, nullptr);
+ if (mPrintDataHandle) {
+ mozilla::nt::AutoMappedView view(mPrintDataHandle, FILE_MAP_ALL_ACCESS);
+ memcpy(view.as<uint8_t>(), aPrintData.Elements(), aPrintData.Length());
+ mPrintDataSize = aPrintData.Length();
+ }
+#else
+ MOZ_ASSERT_UNREACHABLE(
+ "Content Analysis is not supported on non-Windows platforms");
+#endif
+ mOperationTypeForDisplay = OperationType::eOperationPrint;
+ mRequestToken = GenerateRequestToken();
+}
+
nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
nsCString& aDigestString) {
MOZ_DIAGNOSTIC_ASSERT(
@@ -366,22 +432,44 @@ static nsresult ConvertToProtobuf(
requestData->set_digest(sha256Digest.get());
}
- nsString filePath;
- rv = aIn->GetFilePath(filePath);
- NS_ENSURE_SUCCESS(rv, rv);
- if (!filePath.IsEmpty()) {
- std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get();
- aOut->set_file_path(filePathStr);
- auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1);
- if (!filename.empty()) {
- requestData->set_filename(filename);
+ if (analysisType == nsIContentAnalysisRequest::AnalysisType::ePrint) {
+#if XP_WIN
+ uint64_t printDataHandle;
+ MOZ_TRY(aIn->GetPrintDataHandle(&printDataHandle));
+ if (!printDataHandle) {
+ return NS_ERROR_OUT_OF_MEMORY;
}
+ aOut->mutable_print_data()->set_handle(printDataHandle);
+
+ uint64_t printDataSize;
+ MOZ_TRY(aIn->GetPrintDataSize(&printDataSize));
+ aOut->mutable_print_data()->set_size(printDataSize);
+
+ nsString printerName;
+ MOZ_TRY(aIn->GetPrinterName(printerName));
+ requestData->mutable_print_metadata()->set_printer_name(
+ NS_ConvertUTF16toUTF8(printerName).get());
+#else
+ return NS_ERROR_NOT_IMPLEMENTED;
+#endif
} else {
- nsString textContent;
- rv = aIn->GetTextContent(textContent);
+ nsString filePath;
+ rv = aIn->GetFilePath(filePath);
NS_ENSURE_SUCCESS(rv, rv);
- MOZ_ASSERT(!textContent.IsEmpty());
- aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get());
+ if (!filePath.IsEmpty()) {
+ std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get();
+ aOut->set_file_path(filePathStr);
+ auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1);
+ if (!filename.empty()) {
+ requestData->set_filename(filename);
+ }
+ } else {
+ nsString textContent;
+ rv = aIn->GetTextContent(textContent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_ASSERT(!textContent.IsEmpty());
+ aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get());
+ }
}
#ifdef XP_WIN
@@ -412,8 +500,31 @@ static nsresult ConvertToProtobuf(
return NS_OK;
}
+namespace {
+// We don't want this overload to be called for string parameters, so
+// use std::enable_if
+template <typename T>
+typename std::enable_if_t<!std::is_same<std::string, std::decay_t<T>>::value,
+ void>
+LogWithMaxLength(std::stringstream& ss, T value, size_t maxLength) {
+ ss << value;
+}
+
+// 0 indicates no max length
+template <typename T>
+typename std::enable_if_t<std::is_same<std::string, std::decay_t<T>>::value,
+ void>
+LogWithMaxLength(std::stringstream& ss, T value, size_t maxLength) {
+ if (!maxLength || value.length() < maxLength) {
+ ss << value;
+ } else {
+ ss << value.substr(0, maxLength) << " (truncated)";
+ }
+}
+} // namespace
+
static void LogRequest(
- content_analysis::sdk::ContentAnalysisRequest* aPbRequest) {
+ const content_analysis::sdk::ContentAnalysisRequest* aPbRequest) {
// We cannot use Protocol Buffer's DebugString() because we optimize for
// lite runtime.
if (!static_cast<LogModule*>(gContentAnalysisLog)
@@ -425,12 +536,13 @@ static void LogRequest(
ss << "ContentAnalysisRequest:"
<< "\n";
-#define ADD_FIELD(PBUF, NAME, FUNC) \
- ss << " " << (NAME) << ": "; \
- if ((PBUF)->has_##FUNC()) \
- ss << (PBUF)->FUNC() << "\n"; \
- else \
- ss << "<none>" \
+#define ADD_FIELD(PBUF, NAME, FUNC) \
+ ss << " " << (NAME) << ": "; \
+ if ((PBUF)->has_##FUNC()) { \
+ LogWithMaxLength(ss, (PBUF)->FUNC(), 500); \
+ ss << "\n"; \
+ } else \
+ ss << "<none>" \
<< "\n";
#define ADD_EXISTS(PBUF, NAME, FUNC) \
@@ -612,6 +724,12 @@ ContentAnalysisResponse::GetAction(Action* aAction) {
return NS_OK;
}
+NS_IMETHODIMP
+ContentAnalysisResponse::GetCancelError(CancelError* aCancelError) {
+ *aCancelError = mCancelError;
+ return NS_OK;
+}
+
static void LogAcknowledgement(
content_analysis::sdk::ContentAnalysisAcknowledgement* aPbAck) {
if (!static_cast<LogModule*>(gContentAnalysisLog)
@@ -643,6 +761,10 @@ void ContentAnalysisResponse::SetOwner(RefPtr<ContentAnalysis> aOwner) {
mOwner = std::move(aOwner);
}
+void ContentAnalysisResponse::SetCancelError(CancelError aCancelError) {
+ mCancelError = aCancelError;
+}
+
void ContentAnalysisResponse::ResolveWarnAction(bool aAllowContent) {
MOZ_ASSERT(mAction == Action::eWarn);
mAction = aAllowContent ? Action::eAllow : Action::eBlock;
@@ -684,15 +806,17 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
if (mValue.is<NoContentAnalysisResult>()) {
NoContentAnalysisResult result = mValue.as<NoContentAnalysisResult>();
if (Preferences::GetBool(kDefaultAllowPref)) {
- *aShouldAllowContent = result != NoContentAnalysisResult::CANCELED;
+ *aShouldAllowContent =
+ result != NoContentAnalysisResult::DENY_DUE_TO_CANCELED;
} else {
// Note that we allow content if we're unable to get it (for example, if
// there's clipboard content that is not text or file)
*aShouldAllowContent =
- result == NoContentAnalysisResult::CONTENT_ANALYSIS_NOT_ACTIVE ||
- result ==
- NoContentAnalysisResult::CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS ||
- result == NoContentAnalysisResult::ERROR_COULD_NOT_GET_DATA;
+ result == NoContentAnalysisResult::
+ ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE ||
+ result == NoContentAnalysisResult::
+ ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS ||
+ result == NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA;
}
} else {
*aShouldAllowContent =
@@ -735,8 +859,8 @@ ContentAnalysis::UrlFilterResult ContentAnalysis::FilterByUrlLists(
nsIContentAnalysisRequest* aRequest) {
EnsureParsedUrlFilters();
- nsIURI* nsiUrl = nullptr;
- MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(&nsiUrl));
+ nsCOMPtr<nsIURI> nsiUrl;
+ MOZ_ALWAYS_SUCCEEDS(aRequest->GetUrl(getter_AddRefs(nsiUrl)));
nsCString urlString;
nsresult rv = nsiUrl->GetSpec(urlString);
NS_ENSURE_SUCCESS(rv, UrlFilterResult::eDeny);
@@ -825,6 +949,8 @@ NS_IMPL_ISUPPORTS(ContentAnalysisAcknowledgement,
nsIContentAnalysisAcknowledgement);
NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback);
NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult);
+NS_IMPL_ISUPPORTS(ContentAnalysisDiagnosticInfo,
+ nsIContentAnalysisDiagnosticInfo);
NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis, ContentAnalysis);
ContentAnalysis::ContentAnalysis()
@@ -942,6 +1068,7 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
// May be shutting down
return;
}
+ owner->SetLastResult(aResult);
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
bool allow = Preferences::GetBool(kDefaultAllowPref);
@@ -951,6 +1078,20 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
: nsIContentAnalysisResponse::Action::eCanceled,
aRequestToken);
response->SetOwner(owner);
+ nsIContentAnalysisResponse::CancelError cancelError;
+ switch (aResult) {
+ case NS_ERROR_NOT_AVAILABLE:
+ cancelError = nsIContentAnalysisResponse::CancelError::eNoAgent;
+ break;
+ case NS_ERROR_INVALID_SIGNATURE:
+ cancelError =
+ nsIContentAnalysisResponse::CancelError::eInvalidAgentSignature;
+ break;
+ default:
+ cancelError = nsIContentAnalysisResponse::CancelError::eErrorOther;
+ break;
+ }
+ response->SetCancelError(cancelError);
obsServ->NotifyObservers(response, "dlp-response", nullptr);
nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder;
{
@@ -1156,6 +1297,8 @@ void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
LOGE("Content analysis couldn't get request token from response!");
return;
}
+ // Successfully made a request to the agent, so mark that we succeeded
+ mLastResult = NS_OK;
Maybe<CallbackData> maybeCallbackData;
{
@@ -1187,7 +1330,9 @@ void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
LOGD("Content analysis resolving response promise for token %s",
responseRequestToken.get());
- nsIContentAnalysisResponse::Action action = response->GetAction();
+ nsIContentAnalysisResponse::Action action;
+ DebugOnly<nsresult> rv = response->GetAction(&action);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
if (action == nsIContentAnalysisResponse::Action::eWarn) {
@@ -1232,8 +1377,28 @@ NS_IMETHODIMP
ContentAnalysis::AnalyzeContentRequestCallback(
nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
nsIContentAnalysisCallback* aCallback) {
+ MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_ARG(aRequest);
NS_ENSURE_ARG(aCallback);
+ nsresult rv = AnalyzeContentRequestCallbackPrivate(aRequest, aAutoAcknowledge,
+ aCallback);
+ if (NS_FAILED(rv)) {
+ nsCString requestToken;
+ nsresult requestTokenRv = aRequest->GetRequestToken(requestToken);
+ NS_ENSURE_SUCCESS(requestTokenRv, requestTokenRv);
+ CancelWithError(requestToken, rv);
+ }
+ return rv;
+}
+
+nsresult ContentAnalysis::AnalyzeContentRequestCallbackPrivate(
+ nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
+ nsIContentAnalysisCallback* aCallback) {
+ // Make sure we send the notification first, so if we later return
+ // an error the JS will handle it correctly.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
bool isActive;
nsresult rv = GetIsActive(&isActive);
@@ -1242,10 +1407,6 @@ ContentAnalysis::AnalyzeContentRequestCallback(
return NS_ERROR_NOT_AVAILABLE;
}
- nsCOMPtr<nsIObserverService> obsServ =
- mozilla::services::GetObserverService();
- obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
-
MOZ_ASSERT(NS_IsMainThread());
// since we're on the main thread, don't need to synchronize this
int64_t requestCount = ++mRequestCount;
@@ -1333,7 +1494,9 @@ ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken,
return;
}
entry->mResponse->ResolveWarnAction(aAllowContent);
- auto action = entry->mResponse->GetAction();
+ nsIContentAnalysisResponse::Action action;
+ DebugOnly<nsresult> rv = entry->mResponse->GetAction(&action);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
if (entry->mCallbackData.AutoAcknowledge()) {
RefPtr<ContentAnalysisAcknowledgement> acknowledgement =
new ContentAnalysisAcknowledgement(
@@ -1358,6 +1521,181 @@ ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken,
return NS_OK;
}
+#if defined(XP_WIN)
+RefPtr<ContentAnalysis::PrintAllowedPromise>
+ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrintSettings* aPrintSettings) {
+ // Note that the IsChrome() check here excludes a few
+ // common about pages like about:config, about:preferences,
+ // and about:support, but other about: pages may still
+ // go through content analysis.
+ if (aBrowsingContext->IsChrome()) {
+ return PrintAllowedPromise::CreateAndResolve(PrintAllowedResult(true),
+ __func__);
+ }
+ nsCOMPtr<nsIPrintSettings> contentAnalysisPrintSettings;
+ if (NS_WARN_IF(NS_FAILED(aPrintSettings->Clone(
+ getter_AddRefs(contentAnalysisPrintSettings)))) ||
+ NS_WARN_IF(!aBrowsingContext->GetCurrentWindowGlobal())) {
+ return PrintAllowedPromise::CreateAndReject(
+ PrintAllowedError(NS_ERROR_FAILURE), __func__);
+ }
+ contentAnalysisPrintSettings->SetOutputDestination(
+ nsIPrintSettings::OutputDestinationType::kOutputDestinationStream);
+ contentAnalysisPrintSettings->SetOutputFormat(
+ nsIPrintSettings::kOutputFormatPDF);
+ nsCOMPtr<nsIStorageStream> storageStream =
+ do_CreateInstance("@mozilla.org/storagestream;1");
+ if (!storageStream) {
+ return PrintAllowedPromise::CreateAndReject(
+ PrintAllowedError(NS_ERROR_FAILURE), __func__);
+ }
+ // Use segment size of 512K
+ nsresult rv = storageStream->Init(0x80000, UINT32_MAX);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return PrintAllowedPromise::CreateAndReject(PrintAllowedError(rv),
+ __func__);
+ }
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ storageStream->QueryInterface(NS_GET_IID(nsIOutputStream),
+ getter_AddRefs(outputStream));
+ MOZ_ASSERT(outputStream);
+
+ contentAnalysisPrintSettings->SetOutputStream(outputStream.get());
+ RefPtr<dom::CanonicalBrowsingContext> browsingContext = aBrowsingContext;
+ auto promise = MakeRefPtr<PrintAllowedPromise::Private>(__func__);
+ nsCOMPtr<nsIPrintSettings> finalPrintSettings(aPrintSettings);
+ aBrowsingContext
+ ->PrintWithNoContentAnalysis(contentAnalysisPrintSettings, true, nullptr)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [browsingContext, contentAnalysisPrintSettings, finalPrintSettings,
+ promise](
+ dom::MaybeDiscardedBrowsingContext cachedStaticBrowsingContext)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable {
+ nsCOMPtr<nsIOutputStream> outputStream;
+ contentAnalysisPrintSettings->GetOutputStream(
+ getter_AddRefs(outputStream));
+ nsCOMPtr<nsIStorageStream> storageStream =
+ do_QueryInterface(outputStream);
+ MOZ_ASSERT(storageStream);
+ nsTArray<uint8_t> printData;
+ uint32_t length = 0;
+ storageStream->GetLength(&length);
+ if (!printData.SetLength(length, fallible)) {
+ promise->Reject(
+ PrintAllowedError(NS_ERROR_OUT_OF_MEMORY,
+ cachedStaticBrowsingContext),
+ __func__);
+ return;
+ }
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = storageStream->NewInputStream(
+ 0, getter_AddRefs(inputStream));
+ if (NS_FAILED(rv)) {
+ promise->Reject(
+ PrintAllowedError(rv, cachedStaticBrowsingContext),
+ __func__);
+ return;
+ }
+ uint32_t currentPosition = 0;
+ while (currentPosition < length) {
+ uint32_t elementsRead = 0;
+ // Make sure the reinterpret_cast<> below is safe
+ static_assert(std::is_trivially_assignable_v<
+ decltype(*printData.Elements()), char>);
+ rv = inputStream->Read(
+ reinterpret_cast<char*>(printData.Elements()) +
+ currentPosition,
+ length - currentPosition, &elementsRead);
+ if (NS_WARN_IF(NS_FAILED(rv) || !elementsRead)) {
+ promise->Reject(
+ PrintAllowedError(NS_FAILED(rv) ? rv : NS_ERROR_FAILURE,
+ cachedStaticBrowsingContext),
+ __func__);
+ return;
+ }
+ currentPosition += elementsRead;
+ }
+
+ nsString printerName;
+ rv = contentAnalysisPrintSettings->GetPrinterName(printerName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(
+ PrintAllowedError(rv, cachedStaticBrowsingContext),
+ __func__);
+ return;
+ }
+
+ auto* windowParent = browsingContext->GetCurrentWindowGlobal();
+ if (!windowParent) {
+ // The print window may have been closed by the user by now.
+ // Cancel the print.
+ promise->Reject(
+ PrintAllowedError(NS_ERROR_ABORT,
+ cachedStaticBrowsingContext),
+ __func__);
+ return;
+ }
+ nsCOMPtr<nsIURI> uri = windowParent->GetDocumentURI();
+ nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
+ new contentanalysis::ContentAnalysisRequest(
+ std::move(printData), std::move(uri),
+ std::move(printerName), windowParent);
+ auto callback =
+ MakeRefPtr<contentanalysis::ContentAnalysisCallback>(
+ [browsingContext, cachedStaticBrowsingContext, promise,
+ finalPrintSettings = std::move(finalPrintSettings)](
+ nsIContentAnalysisResponse* aResponse)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable {
+ bool shouldAllow = false;
+ DebugOnly<nsresult> rv =
+ aResponse->GetShouldAllowContent(
+ &shouldAllow);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ promise->Resolve(
+ PrintAllowedResult(
+ shouldAllow, cachedStaticBrowsingContext),
+ __func__);
+ },
+ [promise,
+ cachedStaticBrowsingContext](nsresult aError) {
+ promise->Reject(
+ PrintAllowedError(aError,
+ cachedStaticBrowsingContext),
+ __func__);
+ });
+ nsCOMPtr<nsIContentAnalysis> contentAnalysis =
+ mozilla::components::nsIContentAnalysis::Service();
+ if (NS_WARN_IF(!contentAnalysis)) {
+ promise->Reject(
+ PrintAllowedError(rv, cachedStaticBrowsingContext),
+ __func__);
+ } else {
+ bool isActive = false;
+ nsresult rv = contentAnalysis->GetIsActive(&isActive);
+ // Should not be called if content analysis is not active
+ MOZ_ASSERT(isActive);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+ rv = contentAnalysis->AnalyzeContentRequestCallback(
+ contentAnalysisRequest, /* aAutoAcknowledge */ true,
+ callback);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(
+ PrintAllowedError(rv, cachedStaticBrowsingContext),
+ __func__);
+ }
+ }
+ },
+ [promise](nsresult aError) {
+ promise->Reject(PrintAllowedError(aError), __func__);
+ });
+ return promise;
+}
+#endif
+
NS_IMETHODIMP
ContentAnalysisResponse::Acknowledge(
nsIContentAnalysisAcknowledgement* aAcknowledgement) {
@@ -1425,6 +1763,46 @@ nsresult ContentAnalysis::RunAcknowledgeTask(
return rv;
}
+bool ContentAnalysis::LastRequestSucceeded() {
+ return mLastResult != NS_ERROR_NOT_AVAILABLE &&
+ mLastResult != NS_ERROR_INVALID_SIGNATURE &&
+ mLastResult != NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+ContentAnalysis::GetDiagnosticInfo(JSContext* aCx,
+ mozilla::dom::Promise** aPromise) {
+ RefPtr<mozilla::dom::Promise> promise;
+ nsresult rv = MakePromise(aCx, &promise);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mCaClientPromise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](std::shared_ptr<content_analysis::sdk::Client> client) mutable {
+ if (!client) {
+ auto info = MakeRefPtr<ContentAnalysisDiagnosticInfo>(
+ false, EmptyString(), false, 0);
+ promise->MaybeResolve(info);
+ return;
+ }
+ RefPtr<ContentAnalysis> self = GetContentAnalysisFromService();
+ std::string agentPath = client->GetAgentInfo().binary_path;
+ nsString agentWidePath = NS_ConvertUTF8toUTF16(agentPath);
+ auto info = MakeRefPtr<ContentAnalysisDiagnosticInfo>(
+ self->LastRequestSucceeded(), std::move(agentWidePath), false,
+ self ? self->mRequestCount : 0);
+ promise->MaybeResolve(info);
+ },
+ [promise](nsresult rv) {
+ RefPtr<ContentAnalysis> self = GetContentAnalysisFromService();
+ auto info = MakeRefPtr<ContentAnalysisDiagnosticInfo>(
+ false, EmptyString(), rv == NS_ERROR_INVALID_SIGNATURE,
+ self ? self->mRequestCount : 0);
+ promise->MaybeResolve(info);
+ });
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
NS_IMETHODIMP ContentAnalysisCallback::ContentResult(
nsIContentAnalysisResponse* aResponse) {
if (mPromise.isSome()) {
@@ -1448,6 +1826,28 @@ ContentAnalysisCallback::ContentAnalysisCallback(RefPtr<dom::Promise> aPromise)
: mPromise(Some(new nsMainThreadPtrHolder<dom::Promise>(
"content analysis promise", aPromise))) {}
+NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetConnectedToAgent(
+ bool* aConnectedToAgent) {
+ *aConnectedToAgent = mConnectedToAgent;
+ return NS_OK;
+}
+NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetAgentPath(
+ nsAString& aAgentPath) {
+ aAgentPath = mAgentPath;
+ return NS_OK;
+}
+NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetFailedSignatureVerification(
+ bool* aFailedSignatureVerification) {
+ *aFailedSignatureVerification = mFailedSignatureVerification;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentAnalysisDiagnosticInfo::GetRequestCount(
+ int64_t* aRequestCount) {
+ *aRequestCount = mRequestCount;
+ return NS_OK;
+}
+
#undef LOGD
#undef LOGE
} // namespace mozilla::contentanalysis
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.h b/toolkit/components/contentanalysis/ContentAnalysis.h
index 17b6e3fc1b..f2545624fd 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.h
+++ b/toolkit/components/contentanalysis/ContentAnalysis.h
@@ -8,6 +8,8 @@
#include "mozilla/DataMutex.h"
#include "mozilla/MozPromise.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/MaybeDiscarded.h"
#include "mozilla/dom/Promise.h"
#include "nsIContentAnalysis.h"
#include "nsProxyRelease.h"
@@ -18,10 +20,16 @@
#include <regex>
#include <string>
+#ifdef XP_WIN
+# include <windows.h>
+#endif // XP_WIN
+
class nsIPrincipal;
+class nsIPrintSettings;
class ContentAnalysisTest;
namespace mozilla::dom {
+class CanonicalBrowsingContext;
class DataTransfer;
class WindowGlobalParent;
} // namespace mozilla::dom
@@ -34,6 +42,27 @@ class ContentAnalysisResponse;
namespace mozilla::contentanalysis {
+class ContentAnalysisDiagnosticInfo final
+ : public nsIContentAnalysisDiagnosticInfo {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTANALYSISDIAGNOSTICINFO
+ ContentAnalysisDiagnosticInfo(bool aConnectedToAgent, nsString aAgentPath,
+ bool aFailedSignatureVerification,
+ int64_t aRequestCount)
+ : mConnectedToAgent(aConnectedToAgent),
+ mAgentPath(std::move(aAgentPath)),
+ mFailedSignatureVerification(aFailedSignatureVerification),
+ mRequestCount(aRequestCount) {}
+
+ private:
+ ~ContentAnalysisDiagnosticInfo() = default;
+ bool mConnectedToAgent;
+ nsString mAgentPath;
+ bool mFailedSignatureVerification;
+ int64_t mRequestCount;
+};
+
class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
public:
NS_DECL_ISUPPORTS
@@ -43,11 +72,15 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
bool aStringIsFilePath, nsCString aSha256Digest,
nsCOMPtr<nsIURI> aUrl, OperationType aOperationType,
dom::WindowGlobalParent* aWindowGlobalParent);
+ ContentAnalysisRequest(const nsTArray<uint8_t> aPrintData,
+ nsCOMPtr<nsIURI> aUrl, nsString aPrinterName,
+ dom::WindowGlobalParent* aWindowGlobalParent);
static nsresult GetFileDigest(const nsAString& aFilePath,
nsCString& aDigestString);
private:
- ~ContentAnalysisRequest() = default;
+ ~ContentAnalysisRequest();
+
// Remove unneeded copy constructor/assignment
ContentAnalysisRequest(const ContentAnalysisRequest&) = delete;
ContentAnalysisRequest& operator=(ContentAnalysisRequest&) = delete;
@@ -84,7 +117,16 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
// OPERATION_CUSTOMDISPLAYSTRING
nsString mOperationDisplayString;
+ // The name of the printer being printed to
+ nsString mPrinterName;
+
RefPtr<dom::WindowGlobalParent> mWindowGlobalParent;
+#ifdef XP_WIN
+ // The printed data to analyze, in PDF format
+ HANDLE mPrintDataHandle = 0;
+ // The size of the printed data in mPrintDataHandle
+ uint64_t mPrintDataSize = 0;
+#endif
friend class ::ContentAnalysisTest;
};
@@ -105,6 +147,40 @@ class ContentAnalysis final : public nsIContentAnalysis {
ContentAnalysis();
nsCString GetUserActionId();
+ void SetLastResult(nsresult aLastResult) { mLastResult = aLastResult; }
+
+ struct PrintAllowedResult final {
+ bool mAllowed;
+ dom::MaybeDiscarded<dom::BrowsingContext>
+ mCachedStaticDocumentBrowsingContext;
+ PrintAllowedResult(bool aAllowed, dom::MaybeDiscarded<dom::BrowsingContext>
+ aCachedStaticDocumentBrowsingContext)
+ : mAllowed(aAllowed),
+ mCachedStaticDocumentBrowsingContext(
+ aCachedStaticDocumentBrowsingContext) {}
+ explicit PrintAllowedResult(bool aAllowed)
+ : PrintAllowedResult(aAllowed, dom::MaybeDiscardedBrowsingContext()) {}
+ };
+ struct PrintAllowedError final {
+ nsresult mError;
+ dom::MaybeDiscarded<dom::BrowsingContext>
+ mCachedStaticDocumentBrowsingContext;
+ PrintAllowedError(nsresult aError, dom::MaybeDiscarded<dom::BrowsingContext>
+ aCachedStaticDocumentBrowsingContext)
+ : mError(aError),
+ mCachedStaticDocumentBrowsingContext(
+ aCachedStaticDocumentBrowsingContext) {}
+ explicit PrintAllowedError(nsresult aError)
+ : PrintAllowedError(aError, dom::MaybeDiscardedBrowsingContext()) {}
+ };
+ using PrintAllowedPromise =
+ MozPromise<PrintAllowedResult, PrintAllowedError, true>;
+#if defined(XP_WIN)
+ MOZ_CAN_RUN_SCRIPT static RefPtr<PrintAllowedPromise>
+ PrintToPDFToDetermineIfPrintAllowed(
+ dom::CanonicalBrowsingContext* aBrowsingContext,
+ nsIPrintSettings* aPrintSettings);
+#endif // defined(XP_WIN)
private:
~ContentAnalysis();
@@ -114,6 +190,10 @@ class ContentAnalysis final : public nsIContentAnalysis {
nsresult CreateContentAnalysisClient(nsCString&& aPipePathName,
nsString&& aClientSignatureSetting,
bool aIsPerUser);
+ nsresult AnalyzeContentRequestCallbackPrivate(
+ nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
+ nsIContentAnalysisCallback* aCallback);
+
nsresult RunAnalyzeRequestTask(
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
int64_t aRequestCount,
@@ -129,6 +209,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
content_analysis::sdk::ContentAnalysisRequest&& aRequest,
const std::shared_ptr<content_analysis::sdk::Client>& aClient);
void IssueResponse(RefPtr<ContentAnalysisResponse>& response);
+ bool LastRequestSucceeded();
// Did the URL filter completely handle the request or do we need to check
// with the agent.
@@ -147,6 +228,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
bool mClientCreationAttempted;
bool mSetByEnterprise;
+ nsresult mLastResult = NS_OK;
class CallbackData final {
public:
@@ -180,7 +262,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
std::vector<std::regex> mAllowUrlList;
std::vector<std::regex> mDenyUrlList;
- bool mParsedUrlLists;
+ bool mParsedUrlLists = false;
friend class ContentAnalysisResponse;
friend class ::ContentAnalysisTest;
@@ -198,6 +280,7 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
void SetOwner(RefPtr<ContentAnalysis> aOwner);
void DoNotAcknowledge() { mDoNotAcknowledge = true; }
+ void SetCancelError(CancelError aCancelError);
private:
~ContentAnalysisResponse() = default;
@@ -218,7 +301,11 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
// Identifier for the corresponding nsIContentAnalysisRequest
nsCString mRequestToken;
- // ContentAnalysis (or, more precisely, it's Client object) must outlive
+ // If mAction is eCanceled, this is the error explaining why the request was
+ // canceled, or eUserInitiated if the user canceled it.
+ CancelError mCancelError = CancelError::eUserInitiated;
+
+ // ContentAnalysis (or, more precisely, its Client object) must outlive
// the transaction.
RefPtr<ContentAnalysis> mOwner;
diff --git a/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h b/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h
index a7cf812d0b..a554036257 100644
--- a/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h
+++ b/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h
@@ -17,12 +17,12 @@ namespace mozilla {
namespace contentanalysis {
enum class NoContentAnalysisResult : uint8_t {
- CONTENT_ANALYSIS_NOT_ACTIVE,
- CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS,
- CANCELED,
- ERROR_INVALID_JSON_RESPONSE,
- ERROR_COULD_NOT_GET_DATA,
- ERROR_OTHER,
+ ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE,
+ ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS,
+ ALLOW_DUE_TO_COULD_NOT_GET_DATA,
+ DENY_DUE_TO_CANCELED,
+ DENY_DUE_TO_INVALID_JSON_RESPONSE,
+ DENY_DUE_TO_OTHER_ERROR,
LAST_VALUE
};
@@ -59,7 +59,8 @@ class ContentAnalysisResult : public nsIContentAnalysisResult {
}
}
}
- return FromNoResult(NoContentAnalysisResult::ERROR_INVALID_JSON_RESPONSE);
+ return FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_INVALID_JSON_RESPONSE);
}
static RefPtr<ContentAnalysisResult> FromJSONContentAnalysisResponse(
@@ -76,16 +77,21 @@ class ContentAnalysisResult : public nsIContentAnalysisResult {
} else if (shouldAllowValue.isFalse()) {
return FromAction(nsIContentAnalysisResponse::Action::eBlock);
} else {
- return FromNoResult(NoContentAnalysisResult::ERROR_OTHER);
+ return FromNoResult(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
}
}
}
- return FromNoResult(NoContentAnalysisResult::ERROR_INVALID_JSON_RESPONSE);
+ return FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_INVALID_JSON_RESPONSE);
}
static RefPtr<ContentAnalysisResult> FromContentAnalysisResponse(
nsIContentAnalysisResponse* aResponse) {
- if (aResponse->GetShouldAllowContent()) {
+ bool shouldAllowContent = false;
+ DebugOnly<nsresult> rv =
+ aResponse->GetShouldAllowContent(&shouldAllowContent);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (shouldAllowContent) {
return FromAction(nsIContentAnalysisResponse::Action::eAllow);
} else {
return FromAction(nsIContentAnalysisResponse::Action::eBlock);
@@ -159,7 +165,8 @@ struct ParamTraits<mozilla::contentanalysis::ContentAnalysisResult*> {
return true;
}
*aResult = mozilla::contentanalysis::ContentAnalysisResult::FromNoResult(
- mozilla::contentanalysis::NoContentAnalysisResult::ERROR_OTHER);
+ mozilla::contentanalysis::NoContentAnalysisResult::
+ DENY_DUE_TO_OTHER_ERROR);
return ReadParam(aReader, &((*aResult)->mValue));
}
};
diff --git a/toolkit/components/contentanalysis/components.conf b/toolkit/components/contentanalysis/components.conf
index 82236cb1b9..1683ef99d7 100644
--- a/toolkit/components/contentanalysis/components.conf
+++ b/toolkit/components/contentanalysis/components.conf
@@ -11,5 +11,6 @@ Classes = [
'contract_ids': ['@mozilla.org/contentanalysis;1'],
'type': 'mozilla::contentanalysis::ContentAnalysis',
'headers': ['/toolkit/components/contentanalysis/ContentAnalysis.h'],
+ 'overridable': True,
},
]
diff --git a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc
index 6a2e1ccf46..b7a7434685 100644
--- a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc
+++ b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.cc
@@ -1,7 +1,7 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
-// source: analysis.proto
+// source: content_analysis/sdk/analysis.proto
-#include "analysis.pb.h"
+#include "content_analysis/sdk/analysis.pb.h"
#include <algorithm>
@@ -134,9 +134,10 @@ PROTOBUF_CONSTEXPR ContentAnalysisRequest::ContentAnalysisRequest(
, /*decltype(_impl_.user_action_id_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}}
, /*decltype(_impl_.request_data_)*/nullptr
, /*decltype(_impl_.client_metadata_)*/nullptr
+ , /*decltype(_impl_.analysis_connector_)*/0
+ , /*decltype(_impl_.reason_)*/0
, /*decltype(_impl_.expires_at_)*/int64_t{0}
, /*decltype(_impl_.user_action_requests_count_)*/int64_t{0}
- , /*decltype(_impl_.analysis_connector_)*/0
, /*decltype(_impl_.content_data_)*/{}
, /*decltype(_impl_._oneof_case_)*/{}} {}
struct ContentAnalysisRequestDefaultTypeInternal {
@@ -400,6 +401,94 @@ constexpr ClientDownloadRequest_ResourceType ClientDownloadRequest::ResourceType
constexpr ClientDownloadRequest_ResourceType ClientDownloadRequest::ResourceType_MAX;
constexpr int ClientDownloadRequest::ResourceType_ARRAYSIZE;
#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))
+bool ContentAnalysisRequest_Reason_IsValid(int value) {
+ switch (value) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<std::string> ContentAnalysisRequest_Reason_strings[8] = {};
+
+static const char ContentAnalysisRequest_Reason_names[] =
+ "CLIPBOARD_PASTE"
+ "DRAG_AND_DROP"
+ "FILE_PICKER_DIALOG"
+ "NORMAL_DOWNLOAD"
+ "PRINT_PREVIEW_PRINT"
+ "SAVE_AS_DOWNLOAD"
+ "SYSTEM_DIALOG_PRINT"
+ "UNKNOWN";
+
+static const ::PROTOBUF_NAMESPACE_ID::internal::EnumEntry ContentAnalysisRequest_Reason_entries[] = {
+ { {ContentAnalysisRequest_Reason_names + 0, 15}, 1 },
+ { {ContentAnalysisRequest_Reason_names + 15, 13}, 2 },
+ { {ContentAnalysisRequest_Reason_names + 28, 18}, 3 },
+ { {ContentAnalysisRequest_Reason_names + 46, 15}, 6 },
+ { {ContentAnalysisRequest_Reason_names + 61, 19}, 4 },
+ { {ContentAnalysisRequest_Reason_names + 80, 16}, 7 },
+ { {ContentAnalysisRequest_Reason_names + 96, 19}, 5 },
+ { {ContentAnalysisRequest_Reason_names + 115, 7}, 0 },
+};
+
+static const int ContentAnalysisRequest_Reason_entries_by_number[] = {
+ 7, // 0 -> UNKNOWN
+ 0, // 1 -> CLIPBOARD_PASTE
+ 1, // 2 -> DRAG_AND_DROP
+ 2, // 3 -> FILE_PICKER_DIALOG
+ 4, // 4 -> PRINT_PREVIEW_PRINT
+ 6, // 5 -> SYSTEM_DIALOG_PRINT
+ 3, // 6 -> NORMAL_DOWNLOAD
+ 5, // 7 -> SAVE_AS_DOWNLOAD
+};
+
+const std::string& ContentAnalysisRequest_Reason_Name(
+ ContentAnalysisRequest_Reason value) {
+ static const bool dummy =
+ ::PROTOBUF_NAMESPACE_ID::internal::InitializeEnumStrings(
+ ContentAnalysisRequest_Reason_entries,
+ ContentAnalysisRequest_Reason_entries_by_number,
+ 8, ContentAnalysisRequest_Reason_strings);
+ (void) dummy;
+ int idx = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumName(
+ ContentAnalysisRequest_Reason_entries,
+ ContentAnalysisRequest_Reason_entries_by_number,
+ 8, value);
+ return idx == -1 ? ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString() :
+ ContentAnalysisRequest_Reason_strings[idx].get();
+}
+bool ContentAnalysisRequest_Reason_Parse(
+ ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ContentAnalysisRequest_Reason* value) {
+ int int_value;
+ bool success = ::PROTOBUF_NAMESPACE_ID::internal::LookUpEnumValue(
+ ContentAnalysisRequest_Reason_entries, 8, name, &int_value);
+ if (success) {
+ *value = static_cast<ContentAnalysisRequest_Reason>(int_value);
+ }
+ return success;
+}
+#if (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::UNKNOWN;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::CLIPBOARD_PASTE;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::DRAG_AND_DROP;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::FILE_PICKER_DIALOG;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::PRINT_PREVIEW_PRINT;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::SYSTEM_DIALOG_PRINT;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::NORMAL_DOWNLOAD;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::SAVE_AS_DOWNLOAD;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::Reason_MIN;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest::Reason_MAX;
+constexpr int ContentAnalysisRequest::Reason_ARRAYSIZE;
+#endif // (__cplusplus < 201703) && (!defined(_MSC_VER) || (_MSC_VER >= 1900 && _MSC_VER < 1912))
bool ContentAnalysisResponse_Result_TriggeredRule_Action_IsValid(int value) {
switch (value) {
case 0:
@@ -2650,7 +2739,7 @@ class ContentAnalysisRequest::_Internal {
(*has_bits)[0] |= 1u;
}
static void set_has_analysis_connector(HasBits* has_bits) {
- (*has_bits)[0] |= 64u;
+ (*has_bits)[0] |= 16u;
}
static const ::content_analysis::sdk::ContentMetaData& request_data(const ContentAnalysisRequest* msg);
static void set_has_request_data(HasBits* has_bits) {
@@ -2662,12 +2751,15 @@ class ContentAnalysisRequest::_Internal {
}
static const ::content_analysis::sdk::ContentAnalysisRequest_PrintData& print_data(const ContentAnalysisRequest* msg);
static void set_has_expires_at(HasBits* has_bits) {
- (*has_bits)[0] |= 16u;
+ (*has_bits)[0] |= 64u;
}
static void set_has_user_action_id(HasBits* has_bits) {
(*has_bits)[0] |= 2u;
}
static void set_has_user_action_requests_count(HasBits* has_bits) {
+ (*has_bits)[0] |= 128u;
+ }
+ static void set_has_reason(HasBits* has_bits) {
(*has_bits)[0] |= 32u;
}
};
@@ -2716,9 +2808,10 @@ ContentAnalysisRequest::ContentAnalysisRequest(const ContentAnalysisRequest& fro
, decltype(_impl_.user_action_id_){}
, decltype(_impl_.request_data_){nullptr}
, decltype(_impl_.client_metadata_){nullptr}
+ , decltype(_impl_.analysis_connector_){}
+ , decltype(_impl_.reason_){}
, decltype(_impl_.expires_at_){}
, decltype(_impl_.user_action_requests_count_){}
- , decltype(_impl_.analysis_connector_){}
, decltype(_impl_.content_data_){}
, /*decltype(_impl_._oneof_case_)*/{}};
@@ -2745,9 +2838,9 @@ ContentAnalysisRequest::ContentAnalysisRequest(const ContentAnalysisRequest& fro
if (from._internal_has_client_metadata()) {
_this->_impl_.client_metadata_ = new ::content_analysis::sdk::ClientMetadata(*from._impl_.client_metadata_);
}
- ::memcpy(&_impl_.expires_at_, &from._impl_.expires_at_,
- static_cast<size_t>(reinterpret_cast<char*>(&_impl_.analysis_connector_) -
- reinterpret_cast<char*>(&_impl_.expires_at_)) + sizeof(_impl_.analysis_connector_));
+ ::memcpy(&_impl_.analysis_connector_, &from._impl_.analysis_connector_,
+ static_cast<size_t>(reinterpret_cast<char*>(&_impl_.user_action_requests_count_) -
+ reinterpret_cast<char*>(&_impl_.analysis_connector_)) + sizeof(_impl_.user_action_requests_count_));
clear_has_content_data();
switch (from.content_data_case()) {
case kTextContent: {
@@ -2782,9 +2875,10 @@ inline void ContentAnalysisRequest::SharedCtor(
, decltype(_impl_.user_action_id_){}
, decltype(_impl_.request_data_){nullptr}
, decltype(_impl_.client_metadata_){nullptr}
+ , decltype(_impl_.analysis_connector_){0}
+ , decltype(_impl_.reason_){0}
, decltype(_impl_.expires_at_){int64_t{0}}
, decltype(_impl_.user_action_requests_count_){int64_t{0}}
- , decltype(_impl_.analysis_connector_){0}
, decltype(_impl_.content_data_){}
, /*decltype(_impl_._oneof_case_)*/{}
};
@@ -2873,10 +2967,10 @@ void ContentAnalysisRequest::Clear() {
_impl_.client_metadata_->Clear();
}
}
- if (cached_has_bits & 0x00000070u) {
- ::memset(&_impl_.expires_at_, 0, static_cast<size_t>(
- reinterpret_cast<char*>(&_impl_.analysis_connector_) -
- reinterpret_cast<char*>(&_impl_.expires_at_)) + sizeof(_impl_.analysis_connector_));
+ if (cached_has_bits & 0x000000f0u) {
+ ::memset(&_impl_.analysis_connector_, 0, static_cast<size_t>(
+ reinterpret_cast<char*>(&_impl_.user_action_requests_count_) -
+ reinterpret_cast<char*>(&_impl_.analysis_connector_)) + sizeof(_impl_.user_action_requests_count_));
}
clear_content_data();
_impl_._has_bits_.Clear();
@@ -2995,6 +3089,19 @@ const char* ContentAnalysisRequest::_InternalParse(const char* ptr, ::_pbi::Pars
} else
goto handle_unusual;
continue;
+ // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19;
+ case 19:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 152)) {
+ uint64_t val = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ if (PROTOBUF_PREDICT_TRUE(::content_analysis::sdk::ContentAnalysisRequest_Reason_IsValid(val))) {
+ _internal_set_reason(static_cast<::content_analysis::sdk::ContentAnalysisRequest_Reason>(val));
+ } else {
+ ::PROTOBUF_NAMESPACE_ID::internal::WriteVarint(19, val, mutable_unknown_fields());
+ }
+ } else
+ goto handle_unusual;
+ continue;
default:
goto handle_unusual;
} // switch
@@ -3033,7 +3140,7 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize(
}
// optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9;
- if (cached_has_bits & 0x00000040u) {
+ if (cached_has_bits & 0x00000010u) {
target = stream->EnsureSpace(target);
target = ::_pbi::WireFormatLite::WriteEnumToArray(
9, this->_internal_analysis_connector(), target);
@@ -3073,7 +3180,7 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize(
default: ;
}
// optional int64 expires_at = 15;
- if (cached_has_bits & 0x00000010u) {
+ if (cached_has_bits & 0x00000040u) {
target = stream->EnsureSpace(target);
target = ::_pbi::WireFormatLite::WriteInt64ToArray(15, this->_internal_expires_at(), target);
}
@@ -3085,7 +3192,7 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize(
}
// optional int64 user_action_requests_count = 17;
- if (cached_has_bits & 0x00000020u) {
+ if (cached_has_bits & 0x00000080u) {
target = stream->EnsureSpace(target);
target = ::_pbi::WireFormatLite::WriteInt64ToArray(17, this->_internal_user_action_requests_count(), target);
}
@@ -3097,6 +3204,13 @@ uint8_t* ContentAnalysisRequest::_InternalSerialize(
_Internal::print_data(this).GetCachedSize(), target, stream);
}
+ // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19;
+ if (cached_has_bits & 0x00000020u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteEnumToArray(
+ 19, this->_internal_reason(), target);
+ }
+
if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(),
static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target);
@@ -3122,7 +3236,7 @@ size_t ContentAnalysisRequest::ByteSizeLong() const {
}
cached_has_bits = _impl_._has_bits_[0];
- if (cached_has_bits & 0x0000007fu) {
+ if (cached_has_bits & 0x000000ffu) {
// optional string request_token = 5;
if (cached_has_bits & 0x00000001u) {
total_size += 1 +
@@ -3151,22 +3265,28 @@ size_t ContentAnalysisRequest::ByteSizeLong() const {
*_impl_.client_metadata_);
}
- // optional int64 expires_at = 15;
+ // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9;
if (cached_has_bits & 0x00000010u) {
- total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_expires_at());
+ total_size += 1 +
+ ::_pbi::WireFormatLite::EnumSize(this->_internal_analysis_connector());
}
- // optional int64 user_action_requests_count = 17;
+ // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19;
if (cached_has_bits & 0x00000020u) {
total_size += 2 +
- ::_pbi::WireFormatLite::Int64Size(
- this->_internal_user_action_requests_count());
+ ::_pbi::WireFormatLite::EnumSize(this->_internal_reason());
}
- // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9;
+ // optional int64 expires_at = 15;
if (cached_has_bits & 0x00000040u) {
- total_size += 1 +
- ::_pbi::WireFormatLite::EnumSize(this->_internal_analysis_connector());
+ total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_expires_at());
+ }
+
+ // optional int64 user_action_requests_count = 17;
+ if (cached_has_bits & 0x00000080u) {
+ total_size += 2 +
+ ::_pbi::WireFormatLite::Int64Size(
+ this->_internal_user_action_requests_count());
}
}
@@ -3219,7 +3339,7 @@ void ContentAnalysisRequest::MergeFrom(const ContentAnalysisRequest& from) {
_this->_impl_.tags_.MergeFrom(from._impl_.tags_);
cached_has_bits = from._impl_._has_bits_[0];
- if (cached_has_bits & 0x0000007fu) {
+ if (cached_has_bits & 0x000000ffu) {
if (cached_has_bits & 0x00000001u) {
_this->_internal_set_request_token(from._internal_request_token());
}
@@ -3235,13 +3355,16 @@ void ContentAnalysisRequest::MergeFrom(const ContentAnalysisRequest& from) {
from._internal_client_metadata());
}
if (cached_has_bits & 0x00000010u) {
- _this->_impl_.expires_at_ = from._impl_.expires_at_;
+ _this->_impl_.analysis_connector_ = from._impl_.analysis_connector_;
}
if (cached_has_bits & 0x00000020u) {
- _this->_impl_.user_action_requests_count_ = from._impl_.user_action_requests_count_;
+ _this->_impl_.reason_ = from._impl_.reason_;
}
if (cached_has_bits & 0x00000040u) {
- _this->_impl_.analysis_connector_ = from._impl_.analysis_connector_;
+ _this->_impl_.expires_at_ = from._impl_.expires_at_;
+ }
+ if (cached_has_bits & 0x00000080u) {
+ _this->_impl_.user_action_requests_count_ = from._impl_.user_action_requests_count_;
}
_this->_impl_._has_bits_[0] |= cached_has_bits;
}
@@ -3296,8 +3419,8 @@ void ContentAnalysisRequest::InternalSwap(ContentAnalysisRequest* other) {
&other->_impl_.user_action_id_, rhs_arena
);
::PROTOBUF_NAMESPACE_ID::internal::memswap<
- PROTOBUF_FIELD_OFFSET(ContentAnalysisRequest, _impl_.analysis_connector_)
- + sizeof(ContentAnalysisRequest::_impl_.analysis_connector_)
+ PROTOBUF_FIELD_OFFSET(ContentAnalysisRequest, _impl_.user_action_requests_count_)
+ + sizeof(ContentAnalysisRequest::_impl_.user_action_requests_count_)
- PROTOBUF_FIELD_OFFSET(ContentAnalysisRequest, _impl_.request_data_)>(
reinterpret_cast<char*>(&_impl_.request_data_),
reinterpret_cast<char*>(&other->_impl_.request_data_));
diff --git a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h
index 186cc97649..875c632ebf 100644
--- a/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h
+++ b/toolkit/components/contentanalysis/content_analysis/sdk/analysis.pb.h
@@ -1,8 +1,8 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
-// source: analysis.proto
+// source: content_analysis/sdk/analysis.proto
-#ifndef GOOGLE_PROTOBUF_INCLUDED_analysis_2eproto
-#define GOOGLE_PROTOBUF_INCLUDED_analysis_2eproto
+#ifndef GOOGLE_PROTOBUF_INCLUDED_content_5fanalysis_2fsdk_2fanalysis_2eproto
+#define GOOGLE_PROTOBUF_INCLUDED_content_5fanalysis_2fsdk_2fanalysis_2eproto
#include <limits>
#include <string>
@@ -13,7 +13,7 @@
#error incompatible with your Protocol Buffer headers. Please update
#error your headers.
#endif
-#if 3021006 < PROTOBUF_MIN_PROTOC_VERSION
+#if 3021012 < PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers. Please
#error regenerate this file with a newer version of protoc.
@@ -31,7 +31,7 @@
#include <google/protobuf/generated_enum_util.h>
// @@protoc_insertion_point(includes)
#include <google/protobuf/port_def.inc>
-#define PROTOBUF_INTERNAL_EXPORT_analysis_2eproto
+#define PROTOBUF_INTERNAL_EXPORT_content_5fanalysis_2fsdk_2fanalysis_2eproto
PROTOBUF_NAMESPACE_OPEN
namespace internal {
class AnyMetadata;
@@ -39,7 +39,7 @@ class AnyMetadata;
PROTOBUF_NAMESPACE_CLOSE
// Internal implementation detail -- do not use these members.
-struct TableStruct_analysis_2eproto {
+struct TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto {
static const uint32_t offsets[];
};
namespace content_analysis {
@@ -154,6 +154,31 @@ inline const std::string& ClientDownloadRequest_ResourceType_Name(T enum_t_value
}
bool ClientDownloadRequest_ResourceType_Parse(
::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ClientDownloadRequest_ResourceType* value);
+enum ContentAnalysisRequest_Reason : int {
+ ContentAnalysisRequest_Reason_UNKNOWN = 0,
+ ContentAnalysisRequest_Reason_CLIPBOARD_PASTE = 1,
+ ContentAnalysisRequest_Reason_DRAG_AND_DROP = 2,
+ ContentAnalysisRequest_Reason_FILE_PICKER_DIALOG = 3,
+ ContentAnalysisRequest_Reason_PRINT_PREVIEW_PRINT = 4,
+ ContentAnalysisRequest_Reason_SYSTEM_DIALOG_PRINT = 5,
+ ContentAnalysisRequest_Reason_NORMAL_DOWNLOAD = 6,
+ ContentAnalysisRequest_Reason_SAVE_AS_DOWNLOAD = 7
+};
+bool ContentAnalysisRequest_Reason_IsValid(int value);
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest_Reason_Reason_MIN = ContentAnalysisRequest_Reason_UNKNOWN;
+constexpr ContentAnalysisRequest_Reason ContentAnalysisRequest_Reason_Reason_MAX = ContentAnalysisRequest_Reason_SAVE_AS_DOWNLOAD;
+constexpr int ContentAnalysisRequest_Reason_Reason_ARRAYSIZE = ContentAnalysisRequest_Reason_Reason_MAX + 1;
+
+const std::string& ContentAnalysisRequest_Reason_Name(ContentAnalysisRequest_Reason value);
+template<typename T>
+inline const std::string& ContentAnalysisRequest_Reason_Name(T enum_t_value) {
+ static_assert(::std::is_same<T, ContentAnalysisRequest_Reason>::value ||
+ ::std::is_integral<T>::value,
+ "Incorrect type passed to function ContentAnalysisRequest_Reason_Name.");
+ return ContentAnalysisRequest_Reason_Name(static_cast<ContentAnalysisRequest_Reason>(enum_t_value));
+}
+bool ContentAnalysisRequest_Reason_Parse(
+ ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, ContentAnalysisRequest_Reason* value);
enum ContentAnalysisResponse_Result_TriggeredRule_Action : int {
ContentAnalysisResponse_Result_TriggeredRule_Action_ACTION_UNSPECIFIED = 0,
ContentAnalysisResponse_Result_TriggeredRule_Action_REPORT_ONLY = 1,
@@ -448,7 +473,7 @@ class ContentMetaData_PrintMetadata final :
int printer_type_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -717,7 +742,7 @@ class ContentMetaData final :
::content_analysis::sdk::ContentMetaData_PrintMetadata* print_metadata_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -864,7 +889,7 @@ class ClientMetadata_Browser final :
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr machine_user_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -1013,7 +1038,7 @@ class ClientMetadata final :
::content_analysis::sdk::ClientMetadata_Browser* browser_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -1178,7 +1203,7 @@ class ClientDownloadRequest_Resource final :
int type_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -1360,7 +1385,7 @@ class ClientDownloadRequest final :
mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -1517,7 +1542,7 @@ class ContentAnalysisRequest_PrintData final :
int64_t size_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -1637,6 +1662,44 @@ class ContentAnalysisRequest final :
typedef ContentAnalysisRequest_PrintData PrintData;
+ typedef ContentAnalysisRequest_Reason Reason;
+ static constexpr Reason UNKNOWN =
+ ContentAnalysisRequest_Reason_UNKNOWN;
+ static constexpr Reason CLIPBOARD_PASTE =
+ ContentAnalysisRequest_Reason_CLIPBOARD_PASTE;
+ static constexpr Reason DRAG_AND_DROP =
+ ContentAnalysisRequest_Reason_DRAG_AND_DROP;
+ static constexpr Reason FILE_PICKER_DIALOG =
+ ContentAnalysisRequest_Reason_FILE_PICKER_DIALOG;
+ static constexpr Reason PRINT_PREVIEW_PRINT =
+ ContentAnalysisRequest_Reason_PRINT_PREVIEW_PRINT;
+ static constexpr Reason SYSTEM_DIALOG_PRINT =
+ ContentAnalysisRequest_Reason_SYSTEM_DIALOG_PRINT;
+ static constexpr Reason NORMAL_DOWNLOAD =
+ ContentAnalysisRequest_Reason_NORMAL_DOWNLOAD;
+ static constexpr Reason SAVE_AS_DOWNLOAD =
+ ContentAnalysisRequest_Reason_SAVE_AS_DOWNLOAD;
+ static inline bool Reason_IsValid(int value) {
+ return ContentAnalysisRequest_Reason_IsValid(value);
+ }
+ static constexpr Reason Reason_MIN =
+ ContentAnalysisRequest_Reason_Reason_MIN;
+ static constexpr Reason Reason_MAX =
+ ContentAnalysisRequest_Reason_Reason_MAX;
+ static constexpr int Reason_ARRAYSIZE =
+ ContentAnalysisRequest_Reason_Reason_ARRAYSIZE;
+ template<typename T>
+ static inline const std::string& Reason_Name(T enum_t_value) {
+ static_assert(::std::is_same<T, Reason>::value ||
+ ::std::is_integral<T>::value,
+ "Incorrect type passed to function Reason_Name.");
+ return ContentAnalysisRequest_Reason_Name(enum_t_value);
+ }
+ static inline bool Reason_Parse(::PROTOBUF_NAMESPACE_ID::ConstStringParam name,
+ Reason* value) {
+ return ContentAnalysisRequest_Reason_Parse(name, value);
+ }
+
// accessors -------------------------------------------------------
enum : int {
@@ -1645,9 +1708,10 @@ class ContentAnalysisRequest final :
kUserActionIdFieldNumber = 16,
kRequestDataFieldNumber = 10,
kClientMetadataFieldNumber = 12,
+ kAnalysisConnectorFieldNumber = 9,
+ kReasonFieldNumber = 19,
kExpiresAtFieldNumber = 15,
kUserActionRequestsCountFieldNumber = 17,
- kAnalysisConnectorFieldNumber = 9,
kTextContentFieldNumber = 13,
kFilePathFieldNumber = 14,
kPrintDataFieldNumber = 18,
@@ -1748,6 +1812,32 @@ class ContentAnalysisRequest final :
::content_analysis::sdk::ClientMetadata* client_metadata);
::content_analysis::sdk::ClientMetadata* unsafe_arena_release_client_metadata();
+ // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9;
+ bool has_analysis_connector() const;
+ private:
+ bool _internal_has_analysis_connector() const;
+ public:
+ void clear_analysis_connector();
+ ::content_analysis::sdk::AnalysisConnector analysis_connector() const;
+ void set_analysis_connector(::content_analysis::sdk::AnalysisConnector value);
+ private:
+ ::content_analysis::sdk::AnalysisConnector _internal_analysis_connector() const;
+ void _internal_set_analysis_connector(::content_analysis::sdk::AnalysisConnector value);
+ public:
+
+ // optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19;
+ bool has_reason() const;
+ private:
+ bool _internal_has_reason() const;
+ public:
+ void clear_reason();
+ ::content_analysis::sdk::ContentAnalysisRequest_Reason reason() const;
+ void set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value);
+ private:
+ ::content_analysis::sdk::ContentAnalysisRequest_Reason _internal_reason() const;
+ void _internal_set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value);
+ public:
+
// optional int64 expires_at = 15;
bool has_expires_at() const;
private:
@@ -1774,19 +1864,6 @@ class ContentAnalysisRequest final :
void _internal_set_user_action_requests_count(int64_t value);
public:
- // optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9;
- bool has_analysis_connector() const;
- private:
- bool _internal_has_analysis_connector() const;
- public:
- void clear_analysis_connector();
- ::content_analysis::sdk::AnalysisConnector analysis_connector() const;
- void set_analysis_connector(::content_analysis::sdk::AnalysisConnector value);
- private:
- ::content_analysis::sdk::AnalysisConnector _internal_analysis_connector() const;
- void _internal_set_analysis_connector(::content_analysis::sdk::AnalysisConnector value);
- public:
-
// string text_content = 13;
bool has_text_content() const;
private:
@@ -1864,9 +1941,10 @@ class ContentAnalysisRequest final :
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr user_action_id_;
::content_analysis::sdk::ContentMetaData* request_data_;
::content_analysis::sdk::ClientMetadata* client_metadata_;
+ int analysis_connector_;
+ int reason_;
int64_t expires_at_;
int64_t user_action_requests_count_;
- int analysis_connector_;
union ContentDataUnion {
constexpr ContentDataUnion() : _constinit_{} {}
::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
@@ -1878,7 +1956,7 @@ class ContentAnalysisRequest final :
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -2090,7 +2168,7 @@ class ContentAnalysisResponse_Result_TriggeredRule final :
int action_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -2302,7 +2380,7 @@ class ContentAnalysisResponse_Result final :
int status_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -2471,7 +2549,7 @@ class ContentAnalysisResponse final :
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr request_token_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -2708,7 +2786,7 @@ class ContentAnalysisAcknowledgement final :
int status_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -2855,7 +2933,7 @@ class ContentAnalysisCancelRequests final :
::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr user_action_id_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -3042,7 +3120,7 @@ class ChromeToAgent final :
::content_analysis::sdk::ContentAnalysisCancelRequests* cancel_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// -------------------------------------------------------------------
@@ -3189,7 +3267,7 @@ class AgentToChrome final :
::content_analysis::sdk::ContentAnalysisResponse* response_;
};
union { Impl_ _impl_; };
- friend struct ::TableStruct_analysis_2eproto;
+ friend struct ::TableStruct_content_5fanalysis_2fsdk_2fanalysis_2eproto;
};
// ===================================================================
@@ -4268,7 +4346,7 @@ inline void ContentAnalysisRequest::set_allocated_request_token(std::string* req
// optional .content_analysis.sdk.AnalysisConnector analysis_connector = 9;
inline bool ContentAnalysisRequest::_internal_has_analysis_connector() const {
- bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0;
+ bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0;
return value;
}
inline bool ContentAnalysisRequest::has_analysis_connector() const {
@@ -4276,7 +4354,7 @@ inline bool ContentAnalysisRequest::has_analysis_connector() const {
}
inline void ContentAnalysisRequest::clear_analysis_connector() {
_impl_.analysis_connector_ = 0;
- _impl_._has_bits_[0] &= ~0x00000040u;
+ _impl_._has_bits_[0] &= ~0x00000010u;
}
inline ::content_analysis::sdk::AnalysisConnector ContentAnalysisRequest::_internal_analysis_connector() const {
return static_cast< ::content_analysis::sdk::AnalysisConnector >(_impl_.analysis_connector_);
@@ -4287,7 +4365,7 @@ inline ::content_analysis::sdk::AnalysisConnector ContentAnalysisRequest::analys
}
inline void ContentAnalysisRequest::_internal_set_analysis_connector(::content_analysis::sdk::AnalysisConnector value) {
assert(::content_analysis::sdk::AnalysisConnector_IsValid(value));
- _impl_._has_bits_[0] |= 0x00000040u;
+ _impl_._has_bits_[0] |= 0x00000010u;
_impl_.analysis_connector_ = value;
}
inline void ContentAnalysisRequest::set_analysis_connector(::content_analysis::sdk::AnalysisConnector value) {
@@ -4780,7 +4858,7 @@ inline ::content_analysis::sdk::ContentAnalysisRequest_PrintData* ContentAnalysi
// optional int64 expires_at = 15;
inline bool ContentAnalysisRequest::_internal_has_expires_at() const {
- bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0;
+ bool value = (_impl_._has_bits_[0] & 0x00000040u) != 0;
return value;
}
inline bool ContentAnalysisRequest::has_expires_at() const {
@@ -4788,7 +4866,7 @@ inline bool ContentAnalysisRequest::has_expires_at() const {
}
inline void ContentAnalysisRequest::clear_expires_at() {
_impl_.expires_at_ = int64_t{0};
- _impl_._has_bits_[0] &= ~0x00000010u;
+ _impl_._has_bits_[0] &= ~0x00000040u;
}
inline int64_t ContentAnalysisRequest::_internal_expires_at() const {
return _impl_.expires_at_;
@@ -4798,7 +4876,7 @@ inline int64_t ContentAnalysisRequest::expires_at() const {
return _internal_expires_at();
}
inline void ContentAnalysisRequest::_internal_set_expires_at(int64_t value) {
- _impl_._has_bits_[0] |= 0x00000010u;
+ _impl_._has_bits_[0] |= 0x00000040u;
_impl_.expires_at_ = value;
}
inline void ContentAnalysisRequest::set_expires_at(int64_t value) {
@@ -4876,7 +4954,7 @@ inline void ContentAnalysisRequest::set_allocated_user_action_id(std::string* us
// optional int64 user_action_requests_count = 17;
inline bool ContentAnalysisRequest::_internal_has_user_action_requests_count() const {
- bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0;
+ bool value = (_impl_._has_bits_[0] & 0x00000080u) != 0;
return value;
}
inline bool ContentAnalysisRequest::has_user_action_requests_count() const {
@@ -4884,7 +4962,7 @@ inline bool ContentAnalysisRequest::has_user_action_requests_count() const {
}
inline void ContentAnalysisRequest::clear_user_action_requests_count() {
_impl_.user_action_requests_count_ = int64_t{0};
- _impl_._has_bits_[0] &= ~0x00000020u;
+ _impl_._has_bits_[0] &= ~0x00000080u;
}
inline int64_t ContentAnalysisRequest::_internal_user_action_requests_count() const {
return _impl_.user_action_requests_count_;
@@ -4894,7 +4972,7 @@ inline int64_t ContentAnalysisRequest::user_action_requests_count() const {
return _internal_user_action_requests_count();
}
inline void ContentAnalysisRequest::_internal_set_user_action_requests_count(int64_t value) {
- _impl_._has_bits_[0] |= 0x00000020u;
+ _impl_._has_bits_[0] |= 0x00000080u;
_impl_.user_action_requests_count_ = value;
}
inline void ContentAnalysisRequest::set_user_action_requests_count(int64_t value) {
@@ -4902,6 +4980,35 @@ inline void ContentAnalysisRequest::set_user_action_requests_count(int64_t value
// @@protoc_insertion_point(field_set:content_analysis.sdk.ContentAnalysisRequest.user_action_requests_count)
}
+// optional .content_analysis.sdk.ContentAnalysisRequest.Reason reason = 19;
+inline bool ContentAnalysisRequest::_internal_has_reason() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0;
+ return value;
+}
+inline bool ContentAnalysisRequest::has_reason() const {
+ return _internal_has_reason();
+}
+inline void ContentAnalysisRequest::clear_reason() {
+ _impl_.reason_ = 0;
+ _impl_._has_bits_[0] &= ~0x00000020u;
+}
+inline ::content_analysis::sdk::ContentAnalysisRequest_Reason ContentAnalysisRequest::_internal_reason() const {
+ return static_cast< ::content_analysis::sdk::ContentAnalysisRequest_Reason >(_impl_.reason_);
+}
+inline ::content_analysis::sdk::ContentAnalysisRequest_Reason ContentAnalysisRequest::reason() const {
+ // @@protoc_insertion_point(field_get:content_analysis.sdk.ContentAnalysisRequest.reason)
+ return _internal_reason();
+}
+inline void ContentAnalysisRequest::_internal_set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value) {
+ assert(::content_analysis::sdk::ContentAnalysisRequest_Reason_IsValid(value));
+ _impl_._has_bits_[0] |= 0x00000020u;
+ _impl_.reason_ = value;
+}
+inline void ContentAnalysisRequest::set_reason(::content_analysis::sdk::ContentAnalysisRequest_Reason value) {
+ _internal_set_reason(value);
+ // @@protoc_insertion_point(field_set:content_analysis.sdk.ContentAnalysisRequest.reason)
+}
+
inline bool ContentAnalysisRequest::has_content_data() const {
return content_data_case() != CONTENT_DATA_NOT_SET;
}
@@ -5944,6 +6051,7 @@ PROTOBUF_NAMESPACE_OPEN
template <> struct is_proto_enum< ::content_analysis::sdk::ContentMetaData_PrintMetadata_PrinterType> : ::std::true_type {};
template <> struct is_proto_enum< ::content_analysis::sdk::ClientDownloadRequest_ResourceType> : ::std::true_type {};
+template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisRequest_Reason> : ::std::true_type {};
template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisResponse_Result_TriggeredRule_Action> : ::std::true_type {};
template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisResponse_Result_Status> : ::std::true_type {};
template <> struct is_proto_enum< ::content_analysis::sdk::ContentAnalysisAcknowledgement_Status> : ::std::true_type {};
@@ -5955,4 +6063,4 @@ PROTOBUF_NAMESPACE_CLOSE
// @@protoc_insertion_point(global_scope)
#include <google/protobuf/port_undef.inc>
-#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_analysis_2eproto
+#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_content_5fanalysis_2fsdk_2fanalysis_2eproto
diff --git a/toolkit/components/contentanalysis/nsIContentAnalysis.idl b/toolkit/components/contentanalysis/nsIContentAnalysis.idl
index 21f98b88e6..63ac8d12fb 100644
--- a/toolkit/components/contentanalysis/nsIContentAnalysis.idl
+++ b/toolkit/components/contentanalysis/nsIContentAnalysis.idl
@@ -36,7 +36,7 @@ interface nsIContentAnalysisAcknowledgement : nsISupports
readonly attribute nsIContentAnalysisAcknowledgement_FinalAction finalAction;
};
-[scriptable, builtinclass, uuid(89088c61-15f6-4ace-a880-a1b5ea47ca66)]
+[scriptable, uuid(89088c61-15f6-4ace-a880-a1b5ea47ca66)]
interface nsIContentAnalysisResponse : nsISupports
{
// These values must stay synchronized with ContentAnalysisResponse
@@ -52,8 +52,18 @@ interface nsIContentAnalysisResponse : nsISupports
eCanceled = 1001,
};
- [infallible] readonly attribute nsIContentAnalysisResponse_Action action;
- [infallible] readonly attribute boolean shouldAllowContent;
+ cenum CancelError : 32 {
+ eUserInitiated = 0,
+ eNoAgent = 1,
+ eInvalidAgentSignature = 2,
+ eErrorOther = 3,
+ };
+
+ readonly attribute nsIContentAnalysisResponse_Action action;
+ readonly attribute boolean shouldAllowContent;
+ // If action is eCanceled, this is the error explaining why the request was canceled,
+ // or eUserInitiated if the user canceled it.
+ readonly attribute nsIContentAnalysisResponse_CancelError cancelError;
// Identifier for the corresponding nsIContentAnalysisRequest
readonly attribute ACString requestToken;
@@ -121,6 +131,7 @@ interface nsIContentAnalysisRequest : nsISupports
eCustomDisplayString = 0,
eClipboard = 1,
eDroppedText = 2,
+ eOperationPrint = 3,
};
readonly attribute nsIContentAnalysisRequest_OperationType operationTypeForDisplay;
readonly attribute AString operationDisplayString;
@@ -131,6 +142,15 @@ interface nsIContentAnalysisRequest : nsISupports
// Name of file to analyze. Only one of textContent or filePath is defined.
readonly attribute AString filePath;
+ // HANDLE to the printed data in PDF format.
+ readonly attribute unsigned long long printDataHandle;
+
+ // Size of the data stored in printDataHandle.
+ readonly attribute unsigned long long printDataSize;
+
+ // Name of the printer being printed to.
+ readonly attribute AString printerName;
+
// The URL containing the file download/upload or to which web content is
// being uploaded.
readonly attribute nsIURI url;
@@ -166,7 +186,16 @@ interface nsIContentAnalysisCallback : nsISupports
void error(in nsresult aResult);
};
-[scriptable, builtinclass, uuid(61497587-2bba-4a88-acd3-3fbb2cedf163)]
+[scriptable, builtinclass, uuid(a430f6ef-a526-4055-8a82-7741ea757367)]
+interface nsIContentAnalysisDiagnosticInfo : nsISupports
+{
+ [infallible] readonly attribute boolean connectedToAgent;
+ readonly attribute AString agentPath;
+ [infallible] readonly attribute boolean failedSignatureVerification;
+ [infallible] readonly attribute long long requestCount;
+};
+
+[scriptable, uuid(61497587-2bba-4a88-acd3-3fbb2cedf163)]
interface nsIContentAnalysis : nsISupports
{
/**
@@ -182,14 +211,14 @@ interface nsIContentAnalysis : nsISupports
* into the parent process to determine whether content analysis is actually
* active.
*/
- readonly attribute bool mightBeActive;
+ readonly attribute boolean mightBeActive;
/**
* True if content-analysis activation was determined by enterprise policy,
* as opposed to enabled with the `allow-content-analysis` command-line
* parameter.
*/
- attribute bool isSetByEnterprisePolicy;
+ attribute boolean isSetByEnterprisePolicy;
/**
* Consults content analysis server, if any, to request a permission
@@ -210,7 +239,7 @@ interface nsIContentAnalysis : nsISupports
* calling nsIContentAnalysisResponse::acknowledge() if the Promise is resolved.
*/
[implicit_jscontext]
- Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar, in bool aAutoAcknowledge);
+ Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar, in boolean aAutoAcknowledge);
/**
* Same functionality as AnalyzeContentRequest(), but more convenient to call
@@ -226,7 +255,7 @@ interface nsIContentAnalysis : nsISupports
* @param callback
* Callbacks to be called when the agent sends a response message (or when there is an error).
*/
- void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in bool aAutoAcknowledge, in nsIContentAnalysisCallback callback);
+ void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in boolean aAutoAcknowledge, in nsIContentAnalysisCallback callback);
/**
* Cancels the request that is in progress. This may not actually cancel the request
@@ -242,7 +271,7 @@ interface nsIContentAnalysis : nsISupports
* Indicates that the user has responded to a WARN dialog. aAllowContent represents
* whether the user wants to allow the request to go through.
*/
- void respondToWarnDialog(in ACString aRequestToken, in bool aAllowContent);
+ void respondToWarnDialog(in ACString aRequestToken, in boolean aAllowContent);
/**
* Cancels all outstanding DLP requests. Used on shutdown.
@@ -254,4 +283,11 @@ interface nsIContentAnalysis : nsISupports
* given to Gecko on the command line.
*/
void testOnlySetCACmdLineArg(in boolean aVal);
+
+ /**
+ * Gets diagnostic information about content analysis. Returns a
+ * nsIContentAnalysisDiagnosticInfo via the returned promise.
+ */
+ [implicit_jscontext]
+ Promise getDiagnosticInfo();
};
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");
+}