summaryrefslogtreecommitdiffstats
path: root/toolkit/components/contentanalysis
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/contentanalysis')
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.cpp334
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysis.h45
-rw-r--r--toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h1
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser.toml27
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js363
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js202
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js112
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js92
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js92
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js195
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js17
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js64
-rw-r--r--toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js68
-rw-r--r--toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html61
-rw-r--r--toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html41
-rw-r--r--toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html46
-rw-r--r--toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html7
-rw-r--r--toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html95
-rw-r--r--toolkit/components/contentanalysis/tests/browser/head.js124
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp257
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp39
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp87
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp24
-rw-r--r--toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.h (renamed from toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h)8
24 files changed, 2138 insertions, 263 deletions
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp
index 2977072984..4f4c53a324 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.cpp
+++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp
@@ -17,9 +17,11 @@
#include "mozilla/Logging.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Services.h"
+#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_browser.h"
#include "nsAppRunner.h"
+#include "nsBaseClipboard.h"
#include "nsComponentManagerUtils.h"
#include "nsIClassInfoImpl.h"
#include "nsIFile.h"
@@ -28,6 +30,8 @@
#include "nsIOutputStream.h"
#include "nsIPrintSettings.h"
#include "nsIStorageStream.h"
+#include "nsISupportsPrimitives.h"
+#include "nsITransferable.h"
#include "ScopedNSSTypes.h"
#include "xpcpublic.h"
@@ -61,7 +65,6 @@ namespace {
const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled";
const char* kIsPerUserPref = "browser.contentanalysis.is_per_user";
const char* kPipePathNamePref = "browser.contentanalysis.pipe_path_name";
-const char* kDefaultAllowPref = "browser.contentanalysis.default_allow";
const char* kClientSignature = "browser.contentanalysis.client_signature";
const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list";
const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list";
@@ -793,6 +796,17 @@ static bool ShouldAllowAction(
aResponseCode == nsIContentAnalysisResponse::Action::eReportOnly ||
aResponseCode == nsIContentAnalysisResponse::Action::eWarn;
}
+
+static DefaultResult GetDefaultResultFromPref() {
+ uint32_t value = StaticPrefs::browser_contentanalysis_default_result();
+ if (value > static_cast<uint32_t>(DefaultResult::eLastValue)) {
+ LOGE(
+ "Invalid value for browser.contentanalysis.default_result pref "
+ "value");
+ return DefaultResult::eBlock;
+ }
+ return static_cast<DefaultResult>(value);
+}
} // namespace
NS_IMETHODIMP ContentAnalysisResponse::GetShouldAllowContent(
@@ -805,7 +819,7 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
bool* aShouldAllowContent) {
if (mValue.is<NoContentAnalysisResult>()) {
NoContentAnalysisResult result = mValue.as<NoContentAnalysisResult>();
- if (Preferences::GetBool(kDefaultAllowPref)) {
+ if (GetDefaultResultFromPref() == DefaultResult::eAllow) {
*aShouldAllowContent =
result != NoContentAnalysisResult::DENY_DUE_TO_CANCELED;
} else {
@@ -816,6 +830,7 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE ||
result == NoContentAnalysisResult::
ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS ||
+ result == NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE ||
result == NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA;
}
} else {
@@ -1062,7 +1077,7 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
nsresult aResult) {
return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
"ContentAnalysis::CancelWithError",
- [aResult, aRequestToken = std::move(aRequestToken)] {
+ [aResult, aRequestToken = std::move(aRequestToken)]() mutable {
RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
if (!owner) {
// May be shutting down
@@ -1071,12 +1086,24 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
owner->SetLastResult(aResult);
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
- bool allow = Preferences::GetBool(kDefaultAllowPref);
+ DefaultResult defaultResponse = GetDefaultResultFromPref();
+ nsIContentAnalysisResponse::Action action;
+ switch (defaultResponse) {
+ case DefaultResult::eAllow:
+ action = nsIContentAnalysisResponse::Action::eAllow;
+ break;
+ case DefaultResult::eWarn:
+ action = nsIContentAnalysisResponse::Action::eWarn;
+ break;
+ case DefaultResult::eBlock:
+ action = nsIContentAnalysisResponse::Action::eCanceled;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ action = nsIContentAnalysisResponse::Action::eCanceled;
+ }
RefPtr<ContentAnalysisResponse> response =
- ContentAnalysisResponse::FromAction(
- allow ? nsIContentAnalysisResponse::Action::eAllow
- : nsIContentAnalysisResponse::Action::eCanceled,
- aRequestToken);
+ ContentAnalysisResponse::FromAction(action, aRequestToken);
response->SetOwner(owner);
nsIContentAnalysisResponse::CancelError cancelError;
switch (aResult) {
@@ -1092,20 +1119,29 @@ nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
break;
}
response->SetCancelError(cancelError);
- obsServ->NotifyObservers(response, "dlp-response", nullptr);
- nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder;
+ Maybe<CallbackData> maybeCallbackData;
{
auto lock = owner->mCallbackMap.Lock();
- auto callbackData = lock->Extract(aRequestToken);
- if (callbackData.isSome()) {
- callbackHolder = callbackData->TakeCallbackHolder();
+ maybeCallbackData = lock->Extract(aRequestToken);
+ if (maybeCallbackData.isNothing()) {
+ LOGD("Content analysis did not find callback for token %s",
+ aRequestToken.get());
+ return;
}
}
+ if (action == nsIContentAnalysisResponse::Action::eWarn) {
+ owner->SendWarnResponse(std::move(aRequestToken),
+ std::move(*maybeCallbackData), response);
+ return;
+ }
+ nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
+ maybeCallbackData->TakeCallbackHolder();
+ obsServ->NotifyObservers(response, "dlp-response", nullptr);
if (callbackHolder) {
- if (allow) {
- callbackHolder->ContentResult(response);
- } else {
+ if (action == nsIContentAnalysisResponse::Action::eCanceled) {
callbackHolder->Error(aResult);
+ } else {
+ callbackHolder->ContentResult(response);
}
}
}));
@@ -1184,6 +1220,22 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
ConvertToProtobuf(aRequest, GetUserActionId(), aRequestCount, &pbRequest);
NS_ENSURE_SUCCESS(rv, rv);
LogRequest(&pbRequest);
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ // Avoid serializing the string here if no one is observing this message
+ if (obsServ->HasObservers("dlp-request-sent-raw")) {
+ std::string requestString = pbRequest.SerializeAsString();
+ nsTArray<char16_t> requestArray;
+ requestArray.SetLength(requestString.size() + 1);
+ for (size_t i = 0; i < requestString.size(); ++i) {
+ // Since NotifyObservers() expects a null-terminated string,
+ // make sure none of these values are 0.
+ requestArray[i] = requestString[i] + 0xFF00;
+ }
+ requestArray[requestString.size()] = 0;
+ obsServ->NotifyObservers(this, "dlp-request-sent-raw",
+ requestArray.Elements());
+ }
mCaClientPromise->Then(
GetCurrentSerialEventTarget(), __func__,
@@ -1289,6 +1341,20 @@ void ContentAnalysis::DoAnalyzeRequest(
}));
}
+void ContentAnalysis::SendWarnResponse(
+ nsCString&& aResponseRequestToken, CallbackData aCallbackData,
+ RefPtr<ContentAnalysisResponse>& aResponse) {
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ {
+ auto warnResponseDataMap = mWarnResponseDataMap.Lock();
+ warnResponseDataMap->InsertOrUpdate(
+ aResponseRequestToken,
+ WarnResponseData(std::move(aCallbackData), aResponse));
+ }
+ obsServ->NotifyObservers(aResponse, "dlp-response", nullptr);
+}
+
void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
MOZ_ASSERT(NS_IsMainThread());
nsCString responseRequestToken;
@@ -1336,13 +1402,8 @@ void ContentAnalysis::IssueResponse(RefPtr<ContentAnalysisResponse>& response) {
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
if (action == nsIContentAnalysisResponse::Action::eWarn) {
- {
- auto warnResponseDataMap = mWarnResponseDataMap.Lock();
- warnResponseDataMap->InsertOrUpdate(
- responseRequestToken,
- WarnResponseData(std::move(*maybeCallbackData), response));
- }
- obsServ->NotifyObservers(response, "dlp-response", nullptr);
+ SendWarnResponse(std::move(responseRequestToken),
+ std::move(*maybeCallbackData), response);
return;
}
@@ -1696,6 +1757,233 @@ ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed(
}
#endif
+NS_IMPL_ISUPPORTS(ContentAnalysis::SafeContentAnalysisResultCallback,
+ nsIContentAnalysisCallback);
+
+// - true means a content analysis request was fired
+// - false means there is no text data in the transferable
+// - NoContentAnalysisResult means there was an error
+using ClipboardContentAnalysisResult =
+ mozilla::Result<bool, mozilla::contentanalysis::NoContentAnalysisResult>;
+
+NS_IMETHODIMP ContentAnalysis::SafeContentAnalysisResultCallback::ContentResult(
+ nsIContentAnalysisResponse* aResponse) {
+ RefPtr<ContentAnalysisResult> result =
+ ContentAnalysisResult::FromContentAnalysisResponse(aResponse);
+ Callback(result);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ContentAnalysis::SafeContentAnalysisResultCallback::Error(
+ nsresult aError) {
+ Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
+ return NS_OK;
+}
+
+ClipboardContentAnalysisResult CheckClipboardContentAnalysisAsText(
+ uint64_t aInnerWindowId,
+ ContentAnalysis::SafeContentAnalysisResultCallback* aResolver,
+ nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
+ nsITransferable* aTextTrans, const char* aFlavor) {
+ nsCOMPtr<nsISupports> transferData;
+ if (NS_FAILED(
+ aTextTrans->GetTransferData(aFlavor, getter_AddRefs(transferData)))) {
+ return false;
+ }
+ nsCOMPtr<nsISupportsString> textData = do_QueryInterface(transferData);
+ if (MOZ_UNLIKELY(!textData)) {
+ return false;
+ }
+ nsString text;
+ if (NS_FAILED(textData->GetData(text))) {
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ if (text.IsEmpty()) {
+ // Content Analysis doesn't expect to analyze an empty string.
+ // Just approve it.
+ return true;
+ }
+ RefPtr<mozilla::dom::WindowGlobalParent> window =
+ mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
+ if (!window) {
+ // The window has gone away in the meantime
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
+ new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
+ std::move(text), false, EmptyCString(), aDocumentURI,
+ nsIContentAnalysisRequest::OperationType::eClipboard, window);
+ nsresult rv = aContentAnalysis->AnalyzeContentRequestCallback(
+ contentAnalysisRequest, /* aAutoAcknowledge */ true, aResolver);
+ if (NS_FAILED(rv)) {
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ return true;
+}
+
+ClipboardContentAnalysisResult CheckClipboardContentAnalysisAsFile(
+ uint64_t aInnerWindowId,
+ ContentAnalysis::SafeContentAnalysisResultCallback* aResolver,
+ nsIURI* aDocumentURI, nsIContentAnalysis* aContentAnalysis,
+ nsITransferable* aFileTrans) {
+ nsCOMPtr<nsISupports> transferData;
+ nsresult rv =
+ aFileTrans->GetTransferData(kFileMime, getter_AddRefs(transferData));
+ nsString filePath;
+ if (NS_SUCCEEDED(rv)) {
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(transferData)) {
+ rv = file->GetPath(filePath);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("clipboard data had kFileMime but no nsIFile!");
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ }
+ if (NS_FAILED(rv) || filePath.IsEmpty()) {
+ return false;
+ }
+ RefPtr<mozilla::dom::WindowGlobalParent> window =
+ mozilla::dom::WindowGlobalParent::GetByInnerWindowId(aInnerWindowId);
+ if (!window) {
+ // The window has gone away in the meantime
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ // Let the content analysis code calculate the digest
+ nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
+ new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry,
+ std::move(filePath), true, EmptyCString(), aDocumentURI,
+ nsIContentAnalysisRequest::OperationType::eCustomDisplayString,
+ window);
+ rv = aContentAnalysis->AnalyzeContentRequestCallback(
+ contentAnalysisRequest,
+ /* aAutoAcknowledge */ true, aResolver);
+ if (NS_FAILED(rv)) {
+ return mozilla::Err(NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR);
+ }
+ return true;
+}
+
+void ContentAnalysis::CheckClipboardContentAnalysis(
+ nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow,
+ nsITransferable* aTransferable, int32_t aClipboardType,
+ SafeContentAnalysisResultCallback* aResolver) {
+ using namespace mozilla::contentanalysis;
+
+ // Content analysis is only needed if an outside webpage has access to
+ // the data. So, skip content analysis if there is:
+ // - no associated window (for example, scripted clipboard read by system
+ // code)
+ // - the window is a chrome docshell
+ // - the window is being rendered in the parent process (for example,
+ // about:support and the like)
+ if (!aWindow || aWindow->GetBrowsingContext()->IsChrome() ||
+ aWindow->IsInProcess()) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::
+ ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS));
+ return;
+ }
+ nsCOMPtr<nsIContentAnalysis> contentAnalysis =
+ mozilla::components::nsIContentAnalysis::Service();
+ if (!contentAnalysis) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
+ return;
+ }
+
+ bool contentAnalysisIsActive;
+ nsresult rv = contentAnalysis->GetIsActive(&contentAnalysisIsActive);
+ if (MOZ_LIKELY(NS_FAILED(rv) || !contentAnalysisIsActive)) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE));
+ return;
+ }
+
+ uint64_t innerWindowId = aWindow->InnerWindowId();
+ if (mozilla::StaticPrefs::
+ browser_contentanalysis_bypass_for_same_tab_operations()) {
+ mozilla::Maybe<uint64_t> cacheInnerWindowId =
+ aClipboard->GetClipboardCacheInnerWindowId(aClipboardType);
+ if (cacheInnerWindowId.isSome() && *cacheInnerWindowId == innerWindowId) {
+ // If the same page copied this data to the clipboard (and the above
+ // preference is set) we can skip content analysis and immediately allow
+ // this.
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::ALLOW_DUE_TO_SAME_TAB_SOURCE));
+ return;
+ }
+ }
+
+ nsCOMPtr<nsIURI> currentURI = aWindow->Canonical()->GetDocumentURI();
+ nsTArray<nsCString> flavors;
+ rv = aTransferable->FlavorsTransferableCanExport(flavors);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::DENY_DUE_TO_OTHER_ERROR));
+ return;
+ }
+ bool keepChecking = true;
+ if (flavors.Contains(kFileMime)) {
+ auto fileResult = CheckClipboardContentAnalysisAsFile(
+ innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable);
+
+ if (fileResult.isErr()) {
+ aResolver->Callback(
+ ContentAnalysisResult::FromNoResult(fileResult.unwrapErr()));
+ return;
+ }
+ keepChecking = !fileResult.unwrap();
+ }
+ if (keepChecking) {
+ // Failed to get the clipboard data as a file, so try as text
+ auto textResult = CheckClipboardContentAnalysisAsText(
+ innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable,
+ kTextMime);
+ if (textResult.isErr()) {
+ aResolver->Callback(
+ ContentAnalysisResult::FromNoResult(textResult.unwrapErr()));
+ return;
+ }
+ keepChecking = !textResult.unwrap();
+ }
+ if (keepChecking) {
+ // Failed to get the clipboard data as a file or text, so try as html
+ auto htmlResult = CheckClipboardContentAnalysisAsText(
+ innerWindowId, aResolver, currentURI, contentAnalysis, aTransferable,
+ kHTMLMime);
+ if (htmlResult.isErr()) {
+ aResolver->Callback(
+ ContentAnalysisResult::FromNoResult(htmlResult.unwrapErr()));
+ return;
+ }
+ if (!htmlResult.unwrap()) {
+ // Couldn't get file or text or html data from this
+ aResolver->Callback(ContentAnalysisResult::FromNoResult(
+ NoContentAnalysisResult::ALLOW_DUE_TO_COULD_NOT_GET_DATA));
+ return;
+ }
+ }
+}
+
+bool ContentAnalysis::CheckClipboardContentAnalysisSync(
+ nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow,
+ const nsCOMPtr<nsITransferable>& trans, int32_t aClipboardType) {
+ bool requestDone = false;
+ RefPtr<nsIContentAnalysisResult> result;
+ auto callback = mozilla::MakeRefPtr<SafeContentAnalysisResultCallback>(
+ [&requestDone, &result](RefPtr<nsIContentAnalysisResult>&& aResult) {
+ result = std::move(aResult);
+ requestDone = true;
+ });
+ CheckClipboardContentAnalysis(aClipboard, aWindow, trans, aClipboardType,
+ callback);
+ mozilla::SpinEventLoopUntil("CheckClipboardContentAnalysisSync"_ns,
+ [&requestDone]() -> bool { return requestDone; });
+ return result->GetShouldAllowContent();
+}
+
NS_IMETHODIMP
ContentAnalysisResponse::Acknowledge(
nsIContentAnalysisAcknowledgement* aAcknowledgement) {
diff --git a/toolkit/components/contentanalysis/ContentAnalysis.h b/toolkit/components/contentanalysis/ContentAnalysis.h
index f2545624fd..2d8a1891b7 100644
--- a/toolkit/components/contentanalysis/ContentAnalysis.h
+++ b/toolkit/components/contentanalysis/ContentAnalysis.h
@@ -7,6 +7,7 @@
#define mozilla_contentanalysis_h
#include "mozilla/DataMutex.h"
+#include "mozilla/MoveOnlyFunction.h"
#include "mozilla/MozPromise.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/MaybeDiscarded.h"
@@ -24,6 +25,7 @@
# include <windows.h>
#endif // XP_WIN
+class nsBaseClipboard;
class nsIPrincipal;
class nsIPrintSettings;
class ContentAnalysisTest;
@@ -42,6 +44,13 @@ class ContentAnalysisResponse;
namespace mozilla::contentanalysis {
+enum class DefaultResult : uint8_t {
+ eBlock = 0,
+ eWarn = 1,
+ eAllow = 2,
+ eLastValue = 2
+};
+
class ContentAnalysisDiagnosticInfo final
: public nsIContentAnalysisDiagnosticInfo {
public:
@@ -149,6 +158,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
nsCString GetUserActionId();
void SetLastResult(nsresult aLastResult) { mLastResult = aLastResult; }
+#if defined(XP_WIN)
struct PrintAllowedResult final {
bool mAllowed;
dom::MaybeDiscarded<dom::BrowsingContext>
@@ -175,13 +185,42 @@ class ContentAnalysis final : public nsIContentAnalysis {
};
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)
+ class SafeContentAnalysisResultCallback final
+ : public nsIContentAnalysisCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICONTENTANALYSISCALLBACK
+ explicit SafeContentAnalysisResultCallback(
+ std::function<void(RefPtr<nsIContentAnalysisResult>&&)> aResolver)
+ : mResolver(std::move(aResolver)) {}
+ void Callback(RefPtr<nsIContentAnalysisResult>&& aResult) {
+ MOZ_ASSERT(mResolver, "Called SafeContentAnalysisResultCallback twice!");
+ if (auto resolver = std::move(mResolver)) {
+ resolver(std::move(aResult));
+ }
+ }
+
+ private:
+ ~SafeContentAnalysisResultCallback() {
+ MOZ_ASSERT(!mResolver, "SafeContentAnalysisResultCallback never called!");
+ }
+ mozilla::MoveOnlyFunction<void(RefPtr<nsIContentAnalysisResult>&&)>
+ mResolver;
+ };
+ static bool CheckClipboardContentAnalysisSync(
+ nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow,
+ const nsCOMPtr<nsITransferable>& trans, int32_t aClipboardType);
+ static void CheckClipboardContentAnalysis(
+ nsBaseClipboard* aClipboard, mozilla::dom::WindowGlobalParent* aWindow,
+ nsITransferable* aTransferable, int32_t aClipboardType,
+ SafeContentAnalysisResultCallback* aResolver);
+
private:
~ContentAnalysis();
// Remove unneeded copy constructor/assignment
@@ -210,7 +249,6 @@ class ContentAnalysis final : public nsIContentAnalysis {
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.
enum UrlFilterResult { eCheck, eDeny, eAllow };
@@ -259,6 +297,9 @@ class ContentAnalysis final : public nsIContentAnalysis {
RefPtr<ContentAnalysisResponse> mResponse;
};
DataMutex<nsTHashMap<nsCString, WarnResponseData>> mWarnResponseDataMap;
+ void SendWarnResponse(nsCString&& aResponseRequestToken,
+ CallbackData aCallbackData,
+ RefPtr<ContentAnalysisResponse>& aResponse);
std::vector<std::regex> mAllowUrlList;
std::vector<std::regex> mDenyUrlList;
diff --git a/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h b/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h
index a554036257..48e650d778 100644
--- a/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h
+++ b/toolkit/components/contentanalysis/ContentAnalysisIPCTypes.h
@@ -19,6 +19,7 @@ namespace contentanalysis {
enum class NoContentAnalysisResult : uint8_t {
ALLOW_DUE_TO_CONTENT_ANALYSIS_NOT_ACTIVE,
ALLOW_DUE_TO_CONTEXT_EXEMPT_FROM_CONTENT_ANALYSIS,
+ ALLOW_DUE_TO_SAME_TAB_SOURCE,
ALLOW_DUE_TO_COULD_NOT_GET_DATA,
DENY_DUE_TO_CANCELED,
DENY_DUE_TO_INVALID_JSON_RESPONSE,
diff --git a/toolkit/components/contentanalysis/tests/browser/browser.toml b/toolkit/components/contentanalysis/tests/browser/browser.toml
index bdbf350593..7a9a3533d1 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser.toml
+++ b/toolkit/components/contentanalysis/tests/browser/browser.toml
@@ -4,6 +4,33 @@ support-files = [
"head.js",
]
+["browser_clipboard_content_analysis.js"]
+
+["browser_clipboard_paste_file_content_analysis.js"]
+support-files = [
+ "clipboard_paste_file.html",
+]
+
+["browser_clipboard_paste_inputandtextarea_content_analysis.js"]
+support-files = [
+ "clipboard_paste_inputandtextarea.html",
+]
+
+["browser_clipboard_paste_noformatting_content_analysis.js"]
+support-files = [
+ "clipboard_paste_noformatting.html",
+]
+
+["browser_clipboard_paste_prompt_content_analysis.js"]
+support-files = [
+ "clipboard_paste_prompt.html",
+]
+
+["browser_clipboard_read_async_content_analysis.js"]
+support-files = [
+ "clipboard_read_async.html",
+]
+
["browser_content_analysis_policies.js"]
["browser_print_changing_page_content_analysis.js"]
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js
new file mode 100644
index 0000000000..875dbaa6e3
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_content_analysis.js
@@ -0,0 +1,363 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test is used to check copy and paste in editable areas to ensure that non-text
+// types (html and images) are copied to and pasted from the clipboard properly.
+
+var testPage =
+ "<body style='margin: 0'>" +
+ " <img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" +
+ " <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" +
+ "</body>";
+
+let mockCA = makeMockContentAnalysis();
+
+add_setup(async function test_setup() {
+ mockCA = mockContentAnalysisService(mockCA);
+});
+
+async function testClipboardWithContentAnalysis(allowPaste) {
+ mockCA.setupForTest(allowPaste);
+ let tab = BrowserTestUtils.addTab(gBrowser);
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ gBrowser.selectedTab = tab;
+
+ await promiseTabLoadEvent(tab, "data:text/html," + escape(testPage));
+ await SimpleTest.promiseFocus(browser);
+
+ function sendKey(key, code) {
+ return BrowserTestUtils.synthesizeKey(
+ key,
+ { code, accelKey: true },
+ browser
+ );
+ }
+
+ // On windows, HTML clipboard includes extra data.
+ // The values are from widget/windows/nsDataObj.cpp.
+ const htmlPrefix = navigator.platform.includes("Win")
+ ? "<html><body>\n<!--StartFragment-->"
+ : "";
+ const htmlPostfix = navigator.platform.includes("Win")
+ ? "<!--EndFragment-->\n</body>\n</html>"
+ : "";
+
+ await SpecialPowers.spawn(browser, [], () => {
+ var doc = content.document;
+ var main = doc.getElementById("main");
+ main.focus();
+
+ // Select an area of the text.
+ let selection = doc.getSelection();
+ selection.modify("move", "left", "line");
+ selection.modify("move", "right", "character");
+ selection.modify("move", "right", "character");
+ selection.modify("move", "right", "character");
+ selection.modify("extend", "right", "word");
+ selection.modify("extend", "right", "word");
+ });
+
+ // The data is empty as the selection was copied during the event default phase.
+ let copyEventPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "copy",
+ false,
+ event => {
+ return event.clipboardData.mozItemCount == 0;
+ }
+ );
+ await SpecialPowers.spawn(browser, [], () => {});
+ await sendKey("c");
+ await copyEventPromise;
+
+ let pastePromise = SpecialPowers.spawn(
+ browser,
+ [htmlPrefix, htmlPostfix, allowPaste],
+ (htmlPrefixChild, htmlPostfixChild, allowPaste) => {
+ let selection = content.document.getSelection();
+ selection.modify("move", "right", "line");
+
+ return new Promise((resolve, _reject) => {
+ content.addEventListener(
+ "paste",
+ event => {
+ let clipboardData = event.clipboardData;
+ Assert.equal(
+ clipboardData.mozItemCount,
+ 1,
+ "One item on clipboard"
+ );
+ Assert.equal(
+ clipboardData.types.length,
+ 2,
+ "Two types on clipboard"
+ );
+ Assert.equal(
+ clipboardData.types[0],
+ "text/html",
+ "text/html on clipboard"
+ );
+ Assert.equal(
+ clipboardData.types[1],
+ "text/plain",
+ "text/plain on clipboard"
+ );
+ Assert.equal(
+ clipboardData.getData("text/html"),
+ allowPaste
+ ? htmlPrefixChild + "t <b>Bold</b>" + htmlPostfixChild
+ : "",
+ "text/html value"
+ );
+ Assert.equal(
+ clipboardData.getData("text/plain"),
+ allowPaste ? "t Bold" : "",
+ "text/plain value"
+ );
+ resolve();
+ },
+ { capture: true, once: true }
+ );
+ });
+ }
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {});
+
+ await sendKey("v");
+ await pastePromise;
+ // 2 calls because there are two formats on the clipboard
+ is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequest(
+ mockCA.calls[0],
+ htmlPrefix + "t <b>Bold</b>" + htmlPostfix
+ );
+ assertContentAnalysisRequest(mockCA.calls[1], "t Bold");
+ mockCA.clearCalls();
+
+ let copyPromise = SpecialPowers.spawn(browser, [], () => {
+ var main = content.document.getElementById("main");
+
+ Assert.equal(
+ main.innerHTML,
+ "Test <b>Bold</b> After Textt <b>Bold</b>",
+ "Copy and paste html"
+ );
+
+ let selection = content.document.getSelection();
+ selection.modify("extend", "left", "word");
+ selection.modify("extend", "left", "word");
+ selection.modify("extend", "left", "character");
+
+ return new Promise((resolve, _reject) => {
+ content.addEventListener(
+ "cut",
+ event => {
+ event.clipboardData.setData("text/plain", "Some text");
+ event.clipboardData.setData("text/html", "<i>Italic</i> ");
+ selection.deleteFromDocument();
+ event.preventDefault();
+ resolve();
+ },
+ { capture: true, once: true }
+ );
+ });
+ });
+
+ await SpecialPowers.spawn(browser, [], () => {});
+
+ await sendKey("x");
+ await copyPromise;
+
+ pastePromise = SpecialPowers.spawn(
+ browser,
+ [htmlPrefix, htmlPostfix, allowPaste],
+ (htmlPrefixChild, htmlPostfixChild, allowPaste) => {
+ let selection = content.document.getSelection();
+ selection.modify("move", "left", "line");
+
+ return new Promise((resolve, _reject) => {
+ content.addEventListener(
+ "paste",
+ event => {
+ let clipboardData = event.clipboardData;
+ Assert.equal(
+ clipboardData.mozItemCount,
+ 1,
+ "One item on clipboard 2"
+ );
+ Assert.equal(
+ clipboardData.types.length,
+ 2,
+ "Two types on clipboard 2"
+ );
+ Assert.equal(
+ clipboardData.types[0],
+ "text/html",
+ "text/html on clipboard 2"
+ );
+ Assert.equal(
+ clipboardData.types[1],
+ "text/plain",
+ "text/plain on clipboard 2"
+ );
+ Assert.equal(
+ clipboardData.getData("text/html"),
+ allowPaste
+ ? htmlPrefixChild + "<i>Italic</i> " + htmlPostfixChild
+ : "",
+ "text/html value 2"
+ );
+ Assert.equal(
+ clipboardData.getData("text/plain"),
+ allowPaste ? "Some text" : "",
+ "text/plain value 2"
+ );
+ resolve();
+ },
+ { capture: true, once: true }
+ );
+ });
+ }
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {});
+
+ await sendKey("v");
+ await pastePromise;
+ // 2 calls because there are two formats on the clipboard
+ is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequest(
+ mockCA.calls[0],
+ htmlPrefix + "<i>Italic</i> " + htmlPostfix
+ );
+ assertContentAnalysisRequest(mockCA.calls[1], "Some text");
+ mockCA.clearCalls();
+
+ await SpecialPowers.spawn(browser, [], () => {
+ var main = content.document.getElementById("main");
+ Assert.equal(
+ main.innerHTML,
+ "<i>Italic</i> Test <b>Bold</b> After<b></b>",
+ "Copy and paste html 2"
+ );
+ });
+
+ // Next, check that the Copy Image command works.
+
+ // The context menu needs to be opened to properly initialize for the copy
+ // image command to run.
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenuShown = promisePopupShown(contextMenu);
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#img",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ await contextMenuShown;
+
+ document.getElementById("context-copyimage-contents").doCommand();
+
+ contextMenu.hidePopup();
+ await promisePopupHidden(contextMenu);
+
+ // Focus the content again
+ await SimpleTest.promiseFocus(browser);
+
+ pastePromise = SpecialPowers.spawn(
+ browser,
+ [htmlPrefix, htmlPostfix, allowPaste],
+ (htmlPrefixChild, htmlPostfixChild, allowPaste) => {
+ var doc = content.document;
+ var main = doc.getElementById("main");
+ main.focus();
+
+ return new Promise((resolve, reject) => {
+ content.addEventListener(
+ "paste",
+ event => {
+ let clipboardData = event.clipboardData;
+
+ // DataTransfer doesn't support the image types yet, so only text/html
+ // will be present.
+ let clipboardText = clipboardData.getData("text/html");
+ if (allowPaste) {
+ if (
+ clipboardText !==
+ htmlPrefixChild +
+ '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ htmlPostfixChild
+ ) {
+ reject(
+ "Clipboard Data did not contain an image, was " +
+ clipboardText
+ );
+ }
+ } else if (clipboardText !== "") {
+ reject("Clipboard Data should be empty, was " + clipboardText);
+ }
+ resolve();
+ },
+ { capture: true, once: true }
+ );
+ });
+ }
+ );
+
+ await SpecialPowers.spawn(browser, [], () => {});
+ await sendKey("v");
+ await pastePromise;
+ is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequest(
+ mockCA.calls[0],
+ htmlPrefix +
+ '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ htmlPostfix
+ );
+ mockCA.clearCalls();
+
+ // The new content should now include an image.
+ await SpecialPowers.spawn(browser, [], () => {
+ var main = content.document.getElementById("main");
+ Assert.equal(
+ main.innerHTML,
+ '<i>Italic</i> <img id="img" tabindex="1" ' +
+ 'src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+ "Test <b>Bold</b> After<b></b>",
+ "Paste after copy image"
+ );
+ });
+
+ gBrowser.removeCurrentTab();
+}
+
+function assertContentAnalysisRequest(request, expectedText) {
+ is(
+ request.url.spec,
+ "data:text/html," + escape(testPage),
+ "request has correct URL"
+ );
+ is(
+ request.analysisType,
+ Ci.nsIContentAnalysisRequest.eBulkDataEntry,
+ "request has correct analysisType"
+ );
+ is(
+ request.operationTypeForDisplay,
+ Ci.nsIContentAnalysisRequest.eClipboard,
+ "request has correct operationTypeForDisplay"
+ );
+ is(request.filePath, "", "request filePath should be empty");
+ is(request.textContent, expectedText, "request textContent should match");
+ is(request.printDataHandle, 0, "request printDataHandle should not be 0");
+ is(request.printDataSize, 0, "request printDataSize should not be 0");
+ ok(!!request.requestToken.length, "request requestToken should not be empty");
+}
+add_task(async function testClipboardWithContentAnalysisAllow() {
+ await testClipboardWithContentAnalysis(true);
+});
+
+add_task(async function testClipboardWithContentAnalysisBlock() {
+ await testClipboardWithContentAnalysis(false);
+});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js
new file mode 100644
index 0000000000..8441c4d7fd
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_file_content_analysis.js
@@ -0,0 +1,202 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that (real) files can be pasted into chrome/content.
+// Pasting files should also hide all other data from content.
+
+function setClipboard(path) {
+ const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(path);
+
+ const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ trans.addDataFlavor("application/x-moz-file");
+ trans.setTransferData("application/x-moz-file", file);
+
+ trans.addDataFlavor("text/plain");
+ const str = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ str.data = "Alternate";
+ trans.setTransferData("text/plain", str);
+
+ // Write to clipboard.
+ Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
+}
+
+let mockCA = makeMockContentAnalysis();
+
+add_setup(async function test_setup() {
+ mockCA = mockContentAnalysisService(mockCA);
+});
+
+const PAGE_URL =
+ "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html";
+
+function assertContentAnalysisRequest(
+ request,
+ expectedDisplayType,
+ expectedFilePath,
+ expectedText
+) {
+ is(request.url.spec, PAGE_URL, "request has correct URL");
+ is(
+ request.analysisType,
+ Ci.nsIContentAnalysisRequest.eBulkDataEntry,
+ "request has correct analysisType"
+ );
+ is(
+ request.operationTypeForDisplay,
+ expectedDisplayType,
+ "request has correct operationTypeForDisplay"
+ );
+ is(request.filePath, expectedFilePath, "request filePath should match");
+ is(request.textContent, expectedText, "request textContent should match");
+ is(request.printDataHandle, 0, "request printDataHandle should not be 0");
+ is(request.printDataSize, 0, "request printDataSize should not be 0");
+ ok(!!request.requestToken.length, "request requestToken should not be empty");
+}
+function assertContentAnalysisRequestFile(request, expectedFilePath) {
+ assertContentAnalysisRequest(
+ request,
+ Ci.nsIContentAnalysisRequest.eCustomDisplayString,
+ expectedFilePath,
+ ""
+ );
+}
+function assertContentAnalysisRequestText(request, expectedText) {
+ assertContentAnalysisRequest(
+ request,
+ Ci.nsIContentAnalysisRequest.eClipboard,
+ "",
+ expectedText
+ );
+}
+
+async function testClipboardPasteFileWithContentAnalysis(allowPaste) {
+ mockCA.setupForTest(allowPaste);
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.events.dataTransfer.mozFile.enabled", true]],
+ });
+
+ // Create a temporary file that will be pasted.
+ const file = await IOUtils.createUniqueFile(
+ PathUtils.tempDir,
+ "test-file.txt",
+ 0o600
+ );
+ const FILE_TEXT = "Hello World!";
+ await IOUtils.writeUTF8(file, FILE_TEXT);
+
+ // Put the data directly onto the native clipboard to make sure
+ // it isn't handled internally in Gecko somehow.
+ setClipboard(file);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+ let browser = tab.linkedBrowser;
+
+ let resultPromise = SpecialPowers.spawn(browser, [allowPaste], allowPaste => {
+ return new Promise(resolve => {
+ content.document.addEventListener("testresult", event => {
+ resolve(event.detail.result);
+ });
+ content.document.getElementById("pasteAllowed").checked = allowPaste;
+ });
+ });
+
+ // Focus <input> in content
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.getElementById("input").focus();
+ });
+
+ // Paste file into <input> in content
+ await BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser);
+
+ let result = await resultPromise;
+ is(
+ result,
+ allowPaste ? PathUtils.filename(file) : "",
+ "Correctly pasted file in content"
+ );
+ is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequestFile(mockCA.calls[0], file);
+ assertContentAnalysisRequestText(mockCA.calls[1], "Alternate");
+ mockCA.clearCalls();
+
+ // The following part of the test is done in-process (note the use of document here instead of
+ // content.document) so none of this should go through content analysis.
+ var input = document.createElement("input");
+ document.documentElement.appendChild(input);
+ input.focus();
+
+ await new Promise((resolve, _reject) => {
+ input.addEventListener(
+ "paste",
+ function (event) {
+ let dt = event.clipboardData;
+ is(dt.types.length, 3, "number of types");
+ ok(dt.types.includes("text/plain"), "text/plain exists in types");
+ ok(
+ dt.types.includes("application/x-moz-file"),
+ "application/x-moz-file exists in types"
+ );
+ is(dt.types[2], "Files", "Last type should be 'Files'");
+ ok(
+ dt.mozTypesAt(0).contains("text/plain"),
+ "text/plain exists in mozTypesAt"
+ );
+ is(
+ dt.getData("text/plain"),
+ "Alternate",
+ "text/plain returned in getData"
+ );
+ is(
+ dt.mozGetDataAt("text/plain", 0),
+ "Alternate",
+ "text/plain returned in mozGetDataAt"
+ );
+
+ ok(
+ dt.mozTypesAt(0).contains("application/x-moz-file"),
+ "application/x-moz-file exists in mozTypesAt"
+ );
+ let mozFile = dt.mozGetDataAt("application/x-moz-file", 0);
+
+ ok(
+ mozFile instanceof Ci.nsIFile,
+ "application/x-moz-file returned nsIFile with mozGetDataAt"
+ );
+
+ is(
+ mozFile.leafName,
+ PathUtils.filename(file),
+ "nsIFile has correct leafName"
+ );
+
+ is(mozFile.fileSize, FILE_TEXT.length, "nsIFile has correct length");
+
+ resolve();
+ },
+ { capture: true, once: true }
+ );
+
+ EventUtils.synthesizeKey("v", { accelKey: true });
+ });
+ is(mockCA.calls.length, 0, "Correct number of calls to Content Analysis");
+
+ input.remove();
+
+ BrowserTestUtils.removeTab(tab);
+
+ await IOUtils.remove(file);
+}
+
+add_task(async function testClipboardPasteFileWithContentAnalysisAllow() {
+ await testClipboardPasteFileWithContentAnalysis(true);
+});
+
+add_task(async function testClipboardPasteFileWithContentAnalysisBlock() {
+ await testClipboardPasteFileWithContentAnalysis(false);
+});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js
new file mode 100644
index 0000000000..6fe37c3368
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_inputandtextarea_content_analysis.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let mockCA = makeMockContentAnalysis();
+
+add_setup(async function test_setup() {
+ mockCA = mockContentAnalysisService(mockCA);
+});
+
+const PAGE_URL =
+ "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html";
+const CLIPBOARD_TEXT_STRING = "Just some text";
+async function testClipboardPaste(allowPaste) {
+ mockCA.setupForTest(allowPaste);
+
+ setClipboardData();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser, [allowPaste], async allowPaste => {
+ content.document.getElementById("pasteAllowed").checked = allowPaste;
+ });
+ await testPasteWithElementId("testInput", browser, allowPaste);
+ await testPasteWithElementId("testTextArea", browser, allowPaste);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+function setClipboardData() {
+ const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ trans.addDataFlavor("text/plain");
+ const str = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ str.data = CLIPBOARD_TEXT_STRING;
+ trans.setTransferData("text/plain", str);
+
+ // Write to clipboard.
+ Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
+}
+
+async function testPasteWithElementId(elementId, browser, allowPaste) {
+ let resultPromise = SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve => {
+ content.document.addEventListener(
+ "testresult",
+ event => {
+ resolve(event.detail.result);
+ },
+ { once: true }
+ );
+ });
+ });
+
+ // Paste into content
+ await SpecialPowers.spawn(browser, [elementId], async elementId => {
+ content.document.getElementById(elementId).focus();
+ });
+ await BrowserTestUtils.synthesizeKey("v", { accelKey: true }, browser);
+ let result = await resultPromise;
+ is(result, undefined, "Got unexpected result from page");
+
+ // Because we call event.clipboardData.getData in the test, this causes another call to
+ // content analysis.
+ is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING);
+ assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING);
+ mockCA.clearCalls();
+ let value = await getElementValue(browser, elementId);
+ is(
+ value,
+ allowPaste ? CLIPBOARD_TEXT_STRING : "",
+ "element has correct value"
+ );
+}
+
+function assertContentAnalysisRequest(request, expectedText) {
+ is(request.url.spec, PAGE_URL, "request has correct URL");
+ is(
+ request.analysisType,
+ Ci.nsIContentAnalysisRequest.eBulkDataEntry,
+ "request has correct analysisType"
+ );
+ is(
+ request.operationTypeForDisplay,
+ Ci.nsIContentAnalysisRequest.eClipboard,
+ "request has correct operationTypeForDisplay"
+ );
+ is(request.filePath, "", "request filePath should match");
+ is(request.textContent, expectedText, "request textContent should match");
+ is(request.printDataHandle, 0, "request printDataHandle should not be 0");
+ is(request.printDataSize, 0, "request printDataSize should not be 0");
+ ok(!!request.requestToken.length, "request requestToken should not be empty");
+}
+
+async function getElementValue(browser, elementId) {
+ return await SpecialPowers.spawn(browser, [elementId], async elementId => {
+ return content.document.getElementById(elementId).value;
+ });
+}
+
+add_task(async function testClipboardPasteWithContentAnalysisAllow() {
+ await testClipboardPaste(true);
+});
+
+add_task(async function testClipboardPasteWithContentAnalysisBlock() {
+ await testClipboardPaste(false);
+});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js
new file mode 100644
index 0000000000..0eedcddd17
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_noformatting_content_analysis.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let mockCA = makeMockContentAnalysis();
+
+add_setup(async function test_setup() {
+ mockCA = mockContentAnalysisService(mockCA);
+});
+
+const PAGE_URL =
+ "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html";
+async function testClipboardPasteNoFormatting(allowPaste) {
+ mockCA.setupForTest(allowPaste);
+
+ const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ const CLIPBOARD_TEXT_STRING = "Some text";
+ const CLIPBOARD_HTML_STRING = "<b>Some HTML</b>";
+ {
+ trans.addDataFlavor("text/plain");
+ const str = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ str.data = CLIPBOARD_TEXT_STRING;
+ trans.setTransferData("text/plain", str);
+ }
+ {
+ trans.addDataFlavor("text/html");
+ const str = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ str.data = CLIPBOARD_HTML_STRING;
+ trans.setTransferData("text/html", str);
+ }
+
+ // Write to clipboard.
+ Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+ let browser = tab.linkedBrowser;
+ let result = await SpecialPowers.spawn(browser, [allowPaste], allowPaste => {
+ return new Promise(resolve => {
+ content.document.addEventListener("testresult", event => {
+ resolve(event.detail.result);
+ });
+ content.document.getElementById("pasteAllowed").checked = allowPaste;
+ content.document.dispatchEvent(new content.CustomEvent("teststart", {}));
+ });
+ });
+ is(result, true, "Got unexpected result from page");
+
+ // Because we call event.clipboardData.getData in the test, this causes another call to
+ // content analysis.
+ is(mockCA.calls.length, 2, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING);
+ assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+function assertContentAnalysisRequest(request, expectedText) {
+ is(request.url.spec, PAGE_URL, "request has correct URL");
+ is(
+ request.analysisType,
+ Ci.nsIContentAnalysisRequest.eBulkDataEntry,
+ "request has correct analysisType"
+ );
+ is(
+ request.operationTypeForDisplay,
+ Ci.nsIContentAnalysisRequest.eClipboard,
+ "request has correct operationTypeForDisplay"
+ );
+ is(request.filePath, "", "request filePath should match");
+ is(request.textContent, expectedText, "request textContent should match");
+ is(request.printDataHandle, 0, "request printDataHandle should not be 0");
+ is(request.printDataSize, 0, "request printDataSize should not be 0");
+ ok(!!request.requestToken.length, "request requestToken should not be empty");
+}
+
+add_task(
+ async function testClipboardPasteNoFormattingWithContentAnalysisAllow() {
+ await testClipboardPasteNoFormatting(true);
+ }
+);
+
+add_task(
+ async function testClipboardPasteNoFormattingWithContentAnalysisBlock() {
+ await testClipboardPasteNoFormatting(false);
+ }
+);
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js
new file mode 100644
index 0000000000..9e57250cc7
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_paste_prompt_content_analysis.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+let mockCA = makeMockContentAnalysis();
+
+add_setup(async function test_setup() {
+ mockCA = mockContentAnalysisService(mockCA);
+});
+
+// Using an external page so the test can checks that the URL matches in the nsIContentAnalysisRequest
+const PAGE_URL =
+ "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html";
+const CLIPBOARD_TEXT_STRING = "Just some text";
+async function testClipboardPaste(allowPaste) {
+ mockCA.setupForTest(allowPaste);
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+ let browser = tab.linkedBrowser;
+
+ let promptPromise = SpecialPowers.spawn(browser, [], async () => {
+ return content.prompt();
+ });
+
+ let prompt = await PromptTestUtils.waitForPrompt(browser, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ });
+ // Paste text into prompt() in content
+ let pastePromise = new Promise(resolve => {
+ prompt.ui.loginTextbox.addEventListener(
+ "paste",
+ () => {
+ // Since mockCA uses setTimeout before invoking the callback,
+ // do it here too
+ setTimeout(() => {
+ resolve();
+ }, 0);
+ },
+ { once: true }
+ );
+ });
+ let ev = new ClipboardEvent("paste", {
+ dataType: "text/plain",
+ data: CLIPBOARD_TEXT_STRING,
+ });
+ prompt.ui.loginTextbox.dispatchEvent(ev);
+ await pastePromise;
+
+ // Close the prompt
+ await PromptTestUtils.handlePrompt(prompt);
+
+ let result = await promptPromise;
+ is(
+ result,
+ allowPaste ? CLIPBOARD_TEXT_STRING : "",
+ "prompt has correct value"
+ );
+ is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
+ assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING);
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+function assertContentAnalysisRequest(request, expectedText) {
+ is(request.url.spec, PAGE_URL, "request has correct URL");
+ is(
+ request.analysisType,
+ Ci.nsIContentAnalysisRequest.eBulkDataEntry,
+ "request has correct analysisType"
+ );
+ is(
+ request.operationTypeForDisplay,
+ Ci.nsIContentAnalysisRequest.eClipboard,
+ "request has correct operationTypeForDisplay"
+ );
+ is(request.filePath, null, "request filePath should match");
+ is(request.textContent, expectedText, "request textContent should match");
+ is(request.printDataHandle, 0, "request printDataHandle should not be 0");
+ is(request.printDataSize, 0, "request printDataSize should not be 0");
+ ok(!!request.requestToken.length, "request requestToken should not be empty");
+}
+
+add_task(async function testClipboardPasteWithContentAnalysisAllow() {
+ await testClipboardPaste(true);
+});
+
+add_task(async function testClipboardPasteWithContentAnalysisBlock() {
+ await testClipboardPaste(false);
+});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js
new file mode 100644
index 0000000000..7d180a048b
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/browser_clipboard_read_async_content_analysis.js
@@ -0,0 +1,195 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let mockCA = makeMockContentAnalysis();
+
+add_setup(async function test_setup() {
+ mockCA = mockContentAnalysisService(mockCA);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.events.asyncClipboard.readText", true],
+ // This pref turns off the "Paste" popup
+ ["dom.events.testing.asyncClipboard", true],
+ ],
+ });
+});
+
+const PAGE_URL =
+ "https://example.com/browser/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html";
+const CLIPBOARD_TEXT_STRING = "Some plain text";
+const CLIPBOARD_HTML_STRING = "<b>Some HTML</b>";
+async function testClipboardReadAsync(allowPaste) {
+ mockCA.setupForTest(allowPaste);
+
+ setClipboardData();
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+ let browser = tab.linkedBrowser;
+ {
+ let result = await setDataAndStartTest(browser, allowPaste, "read");
+ is(result, true, "Got unexpected result from page for read()");
+
+ is(
+ mockCA.calls.length,
+ 2,
+ "Correct number of calls to Content Analysis for read()"
+ );
+ // On Windows, widget adds extra data into HTML clipboard.
+ let expectedHtml = navigator.platform.includes("Win")
+ ? `<html><body>\n<!--StartFragment-->${CLIPBOARD_HTML_STRING}<!--EndFragment-->\n</body>\n</html>`
+ : CLIPBOARD_HTML_STRING;
+
+ assertContentAnalysisRequest(mockCA.calls[0], expectedHtml);
+ assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING);
+ mockCA.clearCalls();
+ }
+
+ {
+ let result = await setDataAndStartTest(browser, allowPaste, "readText");
+ is(result, true, "Got unexpected result from page for readText()");
+
+ is(
+ mockCA.calls.length,
+ 1,
+ "Correct number of calls to Content Analysis for read()"
+ );
+ assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING);
+ mockCA.clearCalls();
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+async function testClipboardReadAsyncWithErrorHelper() {
+ mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);
+
+ setClipboardData();
+
+ // This test throws a number of exceptions, so tell the framework this is OK.
+ // If an exception is thrown we won't get the right response from setDataAndStartTest()
+ // so this should be safe to do.
+ ignoreAllUncaughtExceptions();
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_URL);
+ let browser = tab.linkedBrowser;
+ {
+ let result = await setDataAndStartTest(browser, false, "read", true);
+ is(result, true, "Got unexpected result from page for read()");
+
+ is(
+ mockCA.calls.length,
+ 2,
+ "Correct number of calls to Content Analysis for read()"
+ );
+ // On Windows, widget adds extra data into HTML clipboard.
+ let expectedHtml = navigator.platform.includes("Win")
+ ? `<html><body>\n<!--StartFragment-->${CLIPBOARD_HTML_STRING}<!--EndFragment-->\n</body>\n</html>`
+ : CLIPBOARD_HTML_STRING;
+
+ assertContentAnalysisRequest(mockCA.calls[0], expectedHtml);
+ assertContentAnalysisRequest(mockCA.calls[1], CLIPBOARD_TEXT_STRING);
+ mockCA.clearCalls();
+ }
+
+ {
+ let result = await setDataAndStartTest(browser, false, "readText", true);
+ is(result, true, "Got unexpected result from page for readText()");
+
+ is(
+ mockCA.calls.length,
+ 1,
+ "Correct number of calls to Content Analysis for read()"
+ );
+ assertContentAnalysisRequest(mockCA.calls[0], CLIPBOARD_TEXT_STRING);
+ mockCA.clearCalls();
+ }
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+function setDataAndStartTest(
+ browser,
+ allowPaste,
+ testType,
+ shouldError = false
+) {
+ return SpecialPowers.spawn(
+ browser,
+ [allowPaste, testType, shouldError],
+ (allowPaste, testType, shouldError) => {
+ return new Promise(resolve => {
+ content.document.addEventListener(
+ "testresult",
+ event => {
+ resolve(event.detail.result);
+ },
+ { once: true }
+ );
+ content.document.getElementById("pasteAllowed").checked = allowPaste;
+ content.document.getElementById("contentAnalysisReturnsError").checked =
+ shouldError;
+ content.document.dispatchEvent(
+ new content.CustomEvent("teststart", {
+ detail: Cu.cloneInto({ testType }, content),
+ })
+ );
+ });
+ }
+ );
+}
+
+function assertContentAnalysisRequest(request, expectedText) {
+ is(request.url.spec, PAGE_URL, "request has correct URL");
+ is(
+ request.analysisType,
+ Ci.nsIContentAnalysisRequest.eBulkDataEntry,
+ "request has correct analysisType"
+ );
+ is(
+ request.operationTypeForDisplay,
+ Ci.nsIContentAnalysisRequest.eClipboard,
+ "request has correct operationTypeForDisplay"
+ );
+ is(request.filePath, "", "request filePath should match");
+ is(request.textContent, expectedText, "request textContent should match");
+ is(request.printDataHandle, 0, "request printDataHandle should not be 0");
+ is(request.printDataSize, 0, "request printDataSize should not be 0");
+ ok(!!request.requestToken.length, "request requestToken should not be empty");
+}
+
+function setClipboardData() {
+ const trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+ Ci.nsITransferable
+ );
+ trans.init(null);
+ {
+ trans.addDataFlavor("text/plain");
+ const str = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ str.data = CLIPBOARD_TEXT_STRING;
+ trans.setTransferData("text/plain", str);
+ }
+ {
+ trans.addDataFlavor("text/html");
+ const str = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ str.data = CLIPBOARD_HTML_STRING;
+ trans.setTransferData("text/html", str);
+ }
+
+ // Write to clipboard.
+ Services.clipboard.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
+}
+
+add_task(async function testClipboardReadAsyncWithContentAnalysisAllow() {
+ await testClipboardReadAsync(true);
+});
+
+add_task(async function testClipboardReadAsyncWithContentAnalysisBlock() {
+ await testClipboardReadAsync(false);
+});
+
+add_task(async function testClipboardReadAsyncWithError() {
+ await testClipboardReadAsyncWithErrorHelper();
+});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js
index b226c0a37a..e7122508e1 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_content_analysis_policies.js
@@ -25,7 +25,8 @@ const kAgentNamePref = "agent_name";
const kClientSignaturePref = "client_signature";
const kPerUserPref = "is_per_user";
const kShowBlockedPref = "show_blocked_result";
-const kDefaultAllowPref = "default_allow";
+const kDefaultResultPref = "default_result";
+const kBypassForSameTabOperationsPref = "bypass_for_same_tab_operations";
const ca = Cc["@mozilla.org/contentanalysis;1"].getService(
Ci.nsIContentAnalysis
@@ -87,7 +88,8 @@ add_task(async function test_ca_enterprise_config() {
ClientSignature: string4,
IsPerUser: true,
ShowBlockedResult: false,
- DefaultAllow: true,
+ DefaultResult: 1,
+ BypassForSameTabOperations: true,
},
},
});
@@ -135,9 +137,16 @@ add_task(async function test_ca_enterprise_config() {
"show blocked match"
);
is(
- Services.prefs.getBoolPref("browser.contentanalysis." + kDefaultAllowPref),
+ Services.prefs.getIntPref("browser.contentanalysis." + kDefaultResultPref),
+ 1,
+ "default result match"
+ );
+ is(
+ Services.prefs.getBoolPref(
+ "browser.contentanalysis." + kBypassForSameTabOperationsPref
+ ),
true,
- "default allow match"
+ "bypass for same tab operations match"
);
PoliciesPrefTracker.stop();
});
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js
index 72a7dcbb91..0f2d846627 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_print_changing_page_content_analysis.js
@@ -12,69 +12,7 @@ const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
Ci.nsIPrintSettingsService
);
-let mockCA = {
- isActive: true,
- mightBeActive: true,
- errorValue: undefined,
-
- setupForTest(shouldAllowRequest) {
- this.shouldAllowRequest = shouldAllowRequest;
- this.errorValue = undefined;
- this.calls = [];
- },
-
- setupForTestWithError(errorValue) {
- this.errorValue = errorValue;
- this.calls = [];
- },
-
- getAction() {
- if (this.shouldAllowRequest === undefined) {
- this.shouldAllowRequest = true;
- }
- return this.shouldAllowRequest
- ? Ci.nsIContentAnalysisResponse.eAllow
- : Ci.nsIContentAnalysisResponse.eBlock;
- },
-
- // nsIContentAnalysis methods
- async analyzeContentRequest(request, _autoAcknowledge) {
- info(
- "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" +
- this.shouldAllowRequest +
- ", this.errorValue=" +
- this.errorValue
- );
- this.calls.push(request);
- if (this.errorValue) {
- throw this.errorValue;
- }
- // Use setTimeout to simulate an async activity
- await new Promise(res => setTimeout(res, 0));
- return makeContentAnalysisResponse(this.getAction(), request.requestToken);
- },
-
- analyzeContentRequestCallback(request, autoAcknowledge, callback) {
- info(
- "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" +
- this.shouldAllowRequest +
- ", this.errorValue=" +
- this.errorValue
- );
- this.calls.push(request);
- if (this.errorValue) {
- throw this.errorValue;
- }
- let response = makeContentAnalysisResponse(
- this.getAction(),
- request.requestToken
- );
- // Use setTimeout to simulate an async activity
- setTimeout(() => {
- callback.contentResult(response);
- }, 0);
- },
-};
+let mockCA = makeMockContentAnalysis();
add_setup(async function test_setup() {
mockCA = mockContentAnalysisService(mockCA);
diff --git a/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js
index 9b4c0ffa60..05897b5ca6 100644
--- a/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js
+++ b/toolkit/components/contentanalysis/tests/browser/browser_print_content_analysis.js
@@ -12,73 +12,7 @@ const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
Ci.nsIPrintSettingsService
);
-let mockCA = {
- isActive: true,
- mightBeActive: true,
- errorValue: undefined,
-
- setupForTest(shouldAllowRequest) {
- this.shouldAllowRequest = shouldAllowRequest;
- this.errorValue = undefined;
- this.calls = [];
- },
-
- setupForTestWithError(errorValue) {
- this.errorValue = errorValue;
- this.calls = [];
- },
-
- clearCalls() {
- this.calls = [];
- },
-
- getAction() {
- if (this.shouldAllowRequest === undefined) {
- this.shouldAllowRequest = true;
- }
- return this.shouldAllowRequest
- ? Ci.nsIContentAnalysisResponse.eAllow
- : Ci.nsIContentAnalysisResponse.eBlock;
- },
-
- // nsIContentAnalysis methods
- async analyzeContentRequest(request, _autoAcknowledge) {
- info(
- "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" +
- this.shouldAllowRequest +
- ", this.errorValue=" +
- this.errorValue
- );
- this.calls.push(request);
- if (this.errorValue) {
- throw this.errorValue;
- }
- // Use setTimeout to simulate an async activity
- await new Promise(res => setTimeout(res, 0));
- return makeContentAnalysisResponse(this.getAction(), request.requestToken);
- },
-
- analyzeContentRequestCallback(request, autoAcknowledge, callback) {
- info(
- "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" +
- this.shouldAllowRequest +
- ", this.errorValue=" +
- this.errorValue
- );
- this.calls.push(request);
- if (this.errorValue) {
- throw this.errorValue;
- }
- let response = makeContentAnalysisResponse(
- this.getAction(),
- request.requestToken
- );
- // Use setTimeout to simulate an async activity
- setTimeout(() => {
- callback.contentResult(response);
- }, 0);
- },
-};
+let mockCA = makeMockContentAnalysis();
add_setup(async function test_setup() {
mockCA = mockContentAnalysisService(mockCA);
diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html
new file mode 100644
index 0000000000..9604633842
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_file.html
@@ -0,0 +1,61 @@
+<html><body>
+<script>
+async function checkPaste(event) {
+ let result = null;
+ try {
+ result = await checkPasteHelper(event);
+ } catch (e) {
+ result = e.toString();
+ }
+
+ document.dispatchEvent(new CustomEvent('testresult', {
+ detail: { result }
+ }));
+}
+
+function is(a, b, msg) {
+ if (!Object.is(a, b)) {
+ throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`);
+ }
+}
+
+async function checkPasteHelper(event) {
+ let dt = event.clipboardData;
+ // Set by injected JS in the test
+ let filePasteAllowed = document.getElementById("pasteAllowed").checked;
+
+ is(dt.types.length, 2, "Correct number of types");
+
+ // TODO: Remove application/x-moz-file from content.
+ is(dt.types[0], "application/x-moz-file", "First type")
+ is(dt.types[1], "Files", "Last type must be Files");
+
+ is(dt.getData("text/plain"), "", "text/plain found with getData");
+ is(dt.getData("application/x-moz-file"), "", "application/x-moz-file found with getData");
+
+ if (!filePasteAllowed) {
+ is(dt.files.length, 0, "No files");
+ } else {
+ is(dt.files.length, 1, "Correct number of files");
+ is(dt.files[0].type, "text/plain", "Correct file type");
+ }
+ is(dt.items.length, 1, "Correct number of items");
+ is(dt.items[0].kind, "file", "Correct item kind");
+ if (!filePasteAllowed) {
+ is(dt.items[0].type, "application/x-moz-file", "Correct item type");
+ return "";
+ }
+ is(dt.items[0].type, "text/plain", "Correct item type");
+
+ let file = dt.files[0];
+ is(await file.text(), "Hello World!", "Pasted file contains right text");
+
+ return file.name;
+}
+</script>
+
+<input id="input" onpaste="checkPaste(event)">
+
+<label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox">
+
+</body></html>
diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html
new file mode 100644
index 0000000000..db93e85955
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_inputandtextarea.html
@@ -0,0 +1,41 @@
+<html>
+<body>
+
+<div id="content">
+ <input
+ id="testInput"
+ type="text" onpaste="handlePaste(event)">
+ <textarea id="testTextArea" onpaste="handlePaste(event)"></textarea>
+
+ <label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox">
+</div>
+<script class="testbody" type="application/javascript">
+function is(a, b, msg) {
+ if (!Object.is(a, b)) {
+ throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`);
+ }
+}
+
+function checkPasteHelper(event) {
+ // Set by injected JS in the test
+ let filePasteAllowed = document.getElementById("pasteAllowed").checked;
+ is(event.clipboardData.getData('text/plain'), filePasteAllowed ? "Just some text" : "", "getData(text/plain) should return plain text");
+ is(event.clipboardData.types.length, 1, "Correct number of types");
+}
+
+function handlePaste(e) {
+ let result = null;
+ try {
+ result = checkPasteHelper(e);
+ } catch (e) {
+ result = e.toString();
+ }
+
+ document.dispatchEvent(new CustomEvent('testresult', {
+ detail: { result }
+ }));
+}
+</script>
+
+</body>
+</html>
diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html
new file mode 100644
index 0000000000..eefc40de85
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_noformatting.html
@@ -0,0 +1,46 @@
+<html>
+<body>
+
+<script class="testbody" type="application/javascript">
+function is(a, b, msg) {
+ if (!Object.is(a, b)) {
+ throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`);
+ }
+}
+
+function checkPasteHelper(event) {
+ is(event.clipboardData.types.indexOf('text/html'), -1, "clipboardData shouldn't have text/html");
+ // Set by injected JS in the test
+ let filePasteAllowed = document.getElementById("pasteAllowed").checked;
+ is(event.clipboardData.getData('text/plain'), filePasteAllowed ? "Some text" : "", "getData(text/plain) should return plain text");
+ return true;
+}
+
+window.addEventListener("paste", e => {
+ let result = null;
+ try {
+ result = checkPasteHelper(e);
+ } catch (e) {
+ result = e.toString();
+ }
+
+ document.dispatchEvent(new CustomEvent('testresult', {
+ detail: { result }
+ }));
+});
+
+document.addEventListener("teststart", _e => {
+ let editable = document.getElementById("editable1");
+ editable.focus();
+
+ window.getSelection().selectAllChildren(editable);
+
+ SpecialPowers.doCommand(window, "cmd_pasteNoFormatting");
+});
+</script>
+
+<div contenteditable="true" id="editable1"><b>Formatted Text</b><br></div>
+
+<label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox">
+</body>
+</html>
diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html
new file mode 100644
index 0000000000..8017cc87a1
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/clipboard_paste_prompt.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+
+<div id="content"></div>
+
+</body>
+</html>
diff --git a/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html b/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html
new file mode 100644
index 0000000000..14414a0f28
--- /dev/null
+++ b/toolkit/components/contentanalysis/tests/browser/clipboard_read_async.html
@@ -0,0 +1,95 @@
+<html>
+<body>
+
+<!--<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>-->
+<script class="testbody" type="application/javascript">
+function is(a, b, msg) {
+ if (!Object.is(a, b)) {
+ throw new Error(`FAIL: expected ${b} got ${a} - ${msg}`);
+ }
+}
+
+function isNotAllowedException(ex) {
+ return /NS_ERROR_CONTENT_BLOCKED/.test(ex.toString()) ||
+ /DataError/.test(ex.toString()) ||
+ /NotAllowedError/.test(ex.toString());
+}
+
+async function checkReadHelper(readResult, gotNotAllowedException, isReadTextTest) {
+ // Set by injected JS in the test
+ let filePasteAllowed = document.getElementById("pasteAllowed").checked;
+ let contentAnalysisReturnsError = document.getElementById("contentAnalysisReturnsError").checked;
+ filePasteAllowed = filePasteAllowed && !contentAnalysisReturnsError;
+ is(gotNotAllowedException, !filePasteAllowed && isReadTextTest, "Should get exception from readText() if not allowed");
+ if (isReadTextTest) {
+ is(readResult, filePasteAllowed ? "Some plain text" : null, "Should get expected text from clipboard.readText()");
+ }
+ else {
+ is(readResult.length, 1, "check number of ClipboardItems in response");
+ is(readResult[0].types.length, 2, "check number of types in ClipboardItem");
+
+ {
+ let text = null;
+ let gotNotAllowedException = false;
+ try {
+ let textBlob = await readResult[0].getType("text/plain");
+ text = await textBlob.text();
+ } catch (ex) {
+ gotNotAllowedException = isNotAllowedException(ex);
+ }
+ is(gotNotAllowedException, !filePasteAllowed, "should get exception from reading text data when blocked");
+ if (filePasteAllowed) {
+ is(text, "Some plain text", "check text/plain data");
+ }
+ }
+
+ {
+ let html = null;
+ let gotNotAllowedException = false;
+ try {
+ let htmlBlob = await readResult[0].getType("text/html");
+ html = await htmlBlob.text();
+ } catch (ex) {
+ gotNotAllowedException = isNotAllowedException(ex);
+ }
+ is(gotNotAllowedException, !filePasteAllowed, "should get exception from reading html data when blocked");
+ if (filePasteAllowed) {
+ const CLIPBOARD_HTML_STRING = "<b>Some HTML</b>";
+ let expectedHtml = navigator.platform.includes("Win")
+ ? `<html><body>\n<!--StartFragment-->${CLIPBOARD_HTML_STRING}<!--EndFragment-->\n</body>\n</html>`
+ : CLIPBOARD_HTML_STRING;
+ is(html, expectedHtml, "check text/html data");
+ }
+ }
+ }
+ return true;
+}
+
+document.addEventListener("teststart", async e => {
+ let isReadTextTest = e.detail.testType == "readText";
+ let gotNotAllowedException = false;
+ let readResult = null;
+ try {
+ let readPromise = isReadTextTest ? navigator.clipboard.readText() : navigator.clipboard.read();
+ readResult = await readPromise;
+ } catch (ex) {
+ gotNotAllowedException = isNotAllowedException(ex);
+ }
+
+ let result = null;
+ try {
+ result = checkReadHelper(readResult, gotNotAllowedException, isReadTextTest);
+ } catch (ex) {
+ result = ex.toString();
+ }
+
+ document.dispatchEvent(new CustomEvent('testresult', {
+ detail: { result }
+ }));
+});
+</script>
+
+<label for="pasteAllowed">Paste allowed?</label><input id="pasteAllowed" type="checkbox">
+<label for="contentAnalysisReturnsError">Content Analysis returns error?</label><input id="contentAnalysisReturnsError" type="checkbox">
+</body>
+</html>
diff --git a/toolkit/components/contentanalysis/tests/browser/head.js b/toolkit/components/contentanalysis/tests/browser/head.js
index e645caa2d7..9422a62ff2 100644
--- a/toolkit/components/contentanalysis/tests/browser/head.js
+++ b/toolkit/components/contentanalysis/tests/browser/head.js
@@ -112,3 +112,127 @@ async function waitForFileToAlmostMatchSize(filePath, expectedSize) {
return Math.abs(fileStat.size - expectedSize) <= maxSizeDifference;
}, "Sizes should (almost) match");
}
+
+function makeMockContentAnalysis() {
+ return {
+ isActive: true,
+ mightBeActive: true,
+ errorValue: undefined,
+
+ setupForTest(shouldAllowRequest) {
+ this.shouldAllowRequest = shouldAllowRequest;
+ this.errorValue = undefined;
+ this.calls = [];
+ },
+
+ setupForTestWithError(errorValue) {
+ this.errorValue = errorValue;
+ this.calls = [];
+ },
+
+ clearCalls() {
+ this.calls = [];
+ },
+
+ getAction() {
+ if (this.shouldAllowRequest === undefined) {
+ this.shouldAllowRequest = true;
+ }
+ return this.shouldAllowRequest
+ ? Ci.nsIContentAnalysisResponse.eAllow
+ : Ci.nsIContentAnalysisResponse.eBlock;
+ },
+
+ // nsIContentAnalysis methods
+ async analyzeContentRequest(request, _autoAcknowledge) {
+ info(
+ "Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" +
+ this.shouldAllowRequest +
+ ", this.errorValue=" +
+ this.errorValue
+ );
+ this.calls.push(request);
+ if (this.errorValue) {
+ throw this.errorValue;
+ }
+ // Use setTimeout to simulate an async activity
+ await new Promise(res => setTimeout(res, 0));
+ return makeContentAnalysisResponse(
+ this.getAction(),
+ request.requestToken
+ );
+ },
+
+ analyzeContentRequestCallback(request, autoAcknowledge, callback) {
+ info(
+ "Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" +
+ this.shouldAllowRequest +
+ ", this.errorValue=" +
+ this.errorValue
+ );
+ this.calls.push(request);
+ if (this.errorValue) {
+ throw this.errorValue;
+ }
+ let response = makeContentAnalysisResponse(
+ this.getAction(),
+ request.requestToken
+ );
+ // Use setTimeout to simulate an async activity
+ setTimeout(() => {
+ callback.contentResult(response);
+ }, 0);
+ },
+ };
+}
+
+function whenTabLoaded(aTab, aCallback) {
+ promiseTabLoadEvent(aTab).then(aCallback);
+}
+
+function promiseTabLoaded(aTab) {
+ return new Promise(resolve => {
+ whenTabLoaded(aTab, resolve);
+ });
+}
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param {object} tab
+ * The tab to load into.
+ * @param {string} [url]
+ * The url to load, or the current url.
+ * @returns {Promise<string>} resolved when the event is handled. Rejected if
+ * a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url) {
+ BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, url);
+ }
+
+ return loaded;
+}
+
+function promisePopupShown(popup) {
+ return BrowserTestUtils.waitForPopupEvent(popup, "shown");
+}
+
+function promisePopupHidden(popup) {
+ return BrowserTestUtils.waitForPopupEvent(popup, "hidden");
+}
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp
index cd083a7779..d974ac78db 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysis.cpp
@@ -8,13 +8,25 @@
#include "mozilla/Assertions.h"
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "nsComponentManagerUtils.h"
#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsIURI.h"
+#include "nsIURIMutator.h"
#include "ContentAnalysis.h"
+#include "SpecialSystemDirectory.h"
+#include "TestContentAnalysisUtils.h"
#include <processenv.h>
#include <synchapi.h>
+#include <vector>
const char* kAllowUrlPref = "browser.contentanalysis.allow_url_regex_list";
const char* kDenyUrlPref = "browser.contentanalysis.deny_url_regex_list";
+const char* kPipePathNamePref = "browser.contentanalysis.pipe_path_name";
+const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled";
+const char* kTimeoutPref = "browser.contentanalysis.agent_timeout";
using namespace mozilla;
using namespace mozilla::contentanalysis;
@@ -24,6 +36,9 @@ class ContentAnalysisTest : public testing::Test {
ContentAnalysisTest() {
auto* logmodule = LogModule::Get("contentanalysis");
logmodule->SetLevel(LogLevel::Verbose);
+ MOZ_ALWAYS_SUCCEEDS(
+ Preferences::SetString(kPipePathNamePref, mPipeName.get()));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetBool(kIsDLPEnabledPref, true));
nsCOMPtr<nsIContentAnalysis> caSvc =
do_GetService("@mozilla.org/contentanalysis;1");
@@ -35,17 +50,39 @@ class ContentAnalysisTest : public testing::Test {
mContentAnalysis->mAllowUrlList = {};
mContentAnalysis->mDenyUrlList = {};
+ MOZ_ALWAYS_SUCCEEDS(mContentAnalysis->TestOnlySetCACmdLineArg(true));
+
MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kAllowUrlPref, ""));
MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, ""));
+
+ bool isActive = false;
+ MOZ_ALWAYS_SUCCEEDS(mContentAnalysis->GetIsActive(&isActive));
+ EXPECT_TRUE(isActive);
+ }
+
+ // Note that the constructor (and SetUp() method) get called once per test,
+ // not once for the whole fixture. Because Firefox does not currently
+ // reconnect to an agent after the DLP pipe is closed (bug 1888293), we only
+ // want to create the agent once and make sure the same process stays alive
+ // through all of these tests.
+ static void SetUpTestSuite() {
+ GeneratePipeName(L"contentanalysissdk-gtest-", mPipeName);
+ mAgentInfo = LaunchAgentNormal(L"block", mPipeName);
}
+ static void TearDownTestSuite() { mAgentInfo.TerminateProcess(); }
+
void TearDown() override {
mContentAnalysis->mParsedUrlLists = false;
mContentAnalysis->mAllowUrlList = {};
mContentAnalysis->mDenyUrlList = {};
+ MOZ_ALWAYS_SUCCEEDS(mContentAnalysis->TestOnlySetCACmdLineArg(false));
+
MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kAllowUrlPref, ""));
MOZ_ALWAYS_SUCCEEDS(Preferences::SetCString(kDenyUrlPref, ""));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::ClearUser(kPipePathNamePref));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::ClearUser(kIsDLPEnabledPref));
}
already_AddRefed<nsIContentAnalysisRequest> CreateRequest(const char* aUrl) {
@@ -62,6 +99,8 @@ class ContentAnalysisTest : public testing::Test {
}
RefPtr<ContentAnalysis> mContentAnalysis;
+ static nsString mPipeName;
+ static MozAgentInfo mAgentInfo;
// Proxies for private members of ContentAnalysis. TEST_F
// creates new subclasses -- they do not inherit `friend`s.
@@ -71,6 +110,8 @@ class ContentAnalysisTest : public testing::Test {
return mContentAnalysis->FilterByUrlLists(aReq);
}
};
+nsString ContentAnalysisTest::mPipeName;
+MozAgentInfo ContentAnalysisTest::mAgentInfo;
TEST_F(ContentAnalysisTest, AllowUrlList) {
MOZ_ALWAYS_SUCCEEDS(
@@ -124,3 +165,219 @@ TEST_F(ContentAnalysisTest, DenyOverridesAllowUrlList) {
CreateRequest("https://example.org/matchme/");
ASSERT_EQ(FilterByUrlLists(car), UrlFilterResult::eDeny);
}
+
+nsCOMPtr<nsIURI> GetExampleDotComURI() {
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "https://example.com"));
+ return uri;
+}
+
+void SendRequestAndExpectResponse(
+ RefPtr<ContentAnalysis> contentAnalysis,
+ const nsCOMPtr<nsIContentAnalysisRequest>& request,
+ Maybe<bool> expectedShouldAllow,
+ Maybe<nsIContentAnalysisResponse::Action> expectedAction) {
+ std::atomic<bool> gotResponse = false;
+ std::atomic<bool> timedOut = false;
+ auto callback = MakeRefPtr<ContentAnalysisCallback>(
+ [&](nsIContentAnalysisResponse* response) {
+ if (expectedShouldAllow.isSome()) {
+ bool shouldAllow = false;
+ MOZ_ALWAYS_SUCCEEDS(response->GetShouldAllowContent(&shouldAllow));
+ EXPECT_EQ(*expectedShouldAllow, shouldAllow);
+ }
+ if (expectedAction.isSome()) {
+ nsIContentAnalysisResponse::Action action;
+ MOZ_ALWAYS_SUCCEEDS(response->GetAction(&action));
+ EXPECT_EQ(*expectedAction, action);
+ }
+ nsCString requestToken, originalRequestToken;
+ MOZ_ALWAYS_SUCCEEDS(response->GetRequestToken(requestToken));
+ MOZ_ALWAYS_SUCCEEDS(request->GetRequestToken(originalRequestToken));
+ EXPECT_EQ(originalRequestToken, requestToken);
+ gotResponse = true;
+ },
+ [&gotResponse](nsresult error) {
+ EXPECT_EQ(NS_OK, error);
+ gotResponse = true;
+ // Make sure that we didn't somehow get passed NS_OK
+ FAIL() << "Got error response";
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(
+ contentAnalysis->AnalyzeContentRequestCallback(request, false, callback));
+ RefPtr<CancelableRunnable> timer =
+ NS_NewCancelableRunnableFunction("Content Analysis timeout", [&] {
+ if (!gotResponse.load()) {
+ timedOut = true;
+ }
+ });
+ NS_DelayedDispatchToCurrentThread(do_AddRef(timer), 10000);
+ mozilla::SpinEventLoopUntil("Waiting for ContentAnalysis result"_ns, [&]() {
+ return gotResponse.load() || timedOut.load();
+ });
+ timer->Cancel();
+ EXPECT_TRUE(gotResponse);
+ EXPECT_FALSE(timedOut);
+}
+
+TEST_F(ContentAnalysisTest, SendAllowedTextToAgent_GetAllowedResponse) {
+ nsCOMPtr<nsIURI> uri = GetExampleDotComURI();
+ nsString allow(L"allow");
+ nsCOMPtr<nsIContentAnalysisRequest> request = new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry, std::move(allow),
+ false, EmptyCString(), uri,
+ nsIContentAnalysisRequest::OperationType::eClipboard, nullptr);
+
+ SendRequestAndExpectResponse(mContentAnalysis, request, Some(true),
+ Some(nsIContentAnalysisResponse::eAllow));
+}
+
+TEST_F(ContentAnalysisTest, SendBlockedTextToAgent_GetBlockResponse) {
+ nsCOMPtr<nsIURI> uri = GetExampleDotComURI();
+ nsString block(L"block");
+ nsCOMPtr<nsIContentAnalysisRequest> request = new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry, std::move(block),
+ false, EmptyCString(), uri,
+ nsIContentAnalysisRequest::OperationType::eClipboard, nullptr);
+
+ SendRequestAndExpectResponse(mContentAnalysis, request, Some(false),
+ Some(nsIContentAnalysisResponse::eBlock));
+}
+
+class RawRequestObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ RawRequestObserver() {}
+
+ const std::vector<content_analysis::sdk::ContentAnalysisRequest>&
+ GetRequests() {
+ return mRequests;
+ }
+
+ private:
+ ~RawRequestObserver() = default;
+ std::vector<content_analysis::sdk::ContentAnalysisRequest> mRequests;
+};
+
+NS_IMPL_ISUPPORTS(RawRequestObserver, nsIObserver);
+
+NS_IMETHODIMP RawRequestObserver::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ std::wstring dataWideString(reinterpret_cast<const wchar_t*>(aData));
+ std::vector<uint8_t> dataVector(dataWideString.size());
+ for (size_t i = 0; i < dataWideString.size(); ++i) {
+ // Since this data is really bytes and not a null-terminated string, the
+ // calling code adds 0xFF00 to every member to ensure there are no 0 values.
+ dataVector[i] = static_cast<uint8_t>(dataWideString[i] - 0xFF00);
+ }
+ content_analysis::sdk::ContentAnalysisRequest request;
+ EXPECT_TRUE(request.ParseFromArray(dataVector.data(), dataVector.size()));
+ mRequests.push_back(std::move(request));
+ return NS_OK;
+}
+
+TEST_F(ContentAnalysisTest, CheckRawRequestWithText) {
+ MOZ_ALWAYS_SUCCEEDS(Preferences::SetInt(kTimeoutPref, 65));
+ nsCOMPtr<nsIURI> uri = GetExampleDotComURI();
+ nsString allow(L"allow");
+ nsCOMPtr<nsIContentAnalysisRequest> request = new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry, std::move(allow),
+ false, EmptyCString(), uri,
+ nsIContentAnalysisRequest::OperationType::eClipboard, nullptr);
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ auto rawRequestObserver = MakeRefPtr<RawRequestObserver>();
+ MOZ_ALWAYS_SUCCEEDS(
+ obsServ->AddObserver(rawRequestObserver, "dlp-request-sent-raw", false));
+ time_t now = time(nullptr);
+
+ SendRequestAndExpectResponse(mContentAnalysis, request, Nothing(), Nothing());
+ auto requests = rawRequestObserver->GetRequests();
+ EXPECT_EQ(static_cast<size_t>(1), requests.size());
+ time_t t = requests[0].expires_at();
+ time_t secs_remaining = t - now;
+ // There should be around 65 seconds remaining
+ EXPECT_LE(abs(secs_remaining - 65), 2);
+ const auto& request_url = requests[0].request_data().url();
+ EXPECT_EQ(uri->GetSpecOrDefault(),
+ nsCString(request_url.data(), request_url.size()));
+ nsCString request_user_action_id(requests[0].user_action_id().data(),
+ requests[0].user_action_id().size());
+ // The user_action_id has a GUID appended to the end, just make sure the
+ // beginning is right.
+ request_user_action_id.Truncate(8);
+ EXPECT_EQ(nsCString("Firefox "), request_user_action_id);
+ const auto& request_text = requests[0].text_content();
+ EXPECT_EQ(nsCString("allow"),
+ nsCString(request_text.data(), request_text.size()));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ obsServ->RemoveObserver(rawRequestObserver, "dlp-request-sent-raw"));
+ MOZ_ALWAYS_SUCCEEDS(Preferences::ClearUser(kTimeoutPref));
+}
+
+TEST_F(ContentAnalysisTest, CheckRawRequestWithFile) {
+ nsCOMPtr<nsIURI> uri = GetExampleDotComURI();
+ nsCOMPtr<nsIFile> file;
+ MOZ_ALWAYS_SUCCEEDS(GetSpecialSystemDirectory(OS_CurrentWorkingDirectory,
+ getter_AddRefs(file)));
+ nsString allowRelativePath(L"allowedFile.txt");
+ MOZ_ALWAYS_SUCCEEDS(file->AppendRelativePath(allowRelativePath));
+ nsString allowPath;
+ MOZ_ALWAYS_SUCCEEDS(file->GetPath(allowPath));
+
+ nsCOMPtr<nsIContentAnalysisRequest> request = new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry, allowPath, true,
+ EmptyCString(), uri, nsIContentAnalysisRequest::OperationType::eClipboard,
+ nullptr);
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ auto rawRequestObserver = MakeRefPtr<RawRequestObserver>();
+ MOZ_ALWAYS_SUCCEEDS(
+ obsServ->AddObserver(rawRequestObserver, "dlp-request-sent-raw", false));
+
+ SendRequestAndExpectResponse(mContentAnalysis, request, Nothing(), Nothing());
+ auto requests = rawRequestObserver->GetRequests();
+ EXPECT_EQ(static_cast<size_t>(1), requests.size());
+ const auto& request_url = requests[0].request_data().url();
+ EXPECT_EQ(uri->GetSpecOrDefault(),
+ nsCString(request_url.data(), request_url.size()));
+ nsCString request_user_action_id(requests[0].user_action_id().data(),
+ requests[0].user_action_id().size());
+ // The user_action_id has a GUID appended to the end, just make sure the
+ // beginning is right.
+ request_user_action_id.Truncate(8);
+ EXPECT_EQ(nsCString("Firefox "), request_user_action_id);
+ const auto& request_file_path = requests[0].file_path();
+ EXPECT_EQ(NS_ConvertUTF16toUTF8(allowPath),
+ nsCString(request_file_path.data(), request_file_path.size()));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ obsServ->RemoveObserver(rawRequestObserver, "dlp-request-sent-raw"));
+}
+
+TEST_F(ContentAnalysisTest, CheckTwoRequestsHaveSameUserActionId) {
+ nsCOMPtr<nsIURI> uri = GetExampleDotComURI();
+ nsString allow(L"allow");
+ nsCOMPtr<nsIContentAnalysisRequest> request = new ContentAnalysisRequest(
+ nsIContentAnalysisRequest::AnalysisType::eBulkDataEntry, std::move(allow),
+ false, EmptyCString(), uri,
+ nsIContentAnalysisRequest::OperationType::eClipboard, nullptr);
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ auto rawRequestObserver = MakeRefPtr<RawRequestObserver>();
+ MOZ_ALWAYS_SUCCEEDS(
+ obsServ->AddObserver(rawRequestObserver, "dlp-request-sent-raw", false));
+
+ SendRequestAndExpectResponse(mContentAnalysis, request, Nothing(), Nothing());
+ SendRequestAndExpectResponse(mContentAnalysis, request, Nothing(), Nothing());
+ auto requests = rawRequestObserver->GetRequests();
+ EXPECT_EQ(static_cast<size_t>(2), requests.size());
+ EXPECT_EQ(requests[0].user_action_id(), requests[1].user_action_id());
+
+ MOZ_ALWAYS_SUCCEEDS(
+ obsServ->RemoveObserver(rawRequestObserver, "dlp-request-sent-raw"));
+}
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp
index 5b2b76b963..d7a41cc5ff 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.cpp
@@ -8,29 +8,12 @@
#include "mozilla/Assertions.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "content_analysis/sdk/analysis_client.h"
-#include "TestContentAnalysisAgent.h"
+#include "TestContentAnalysisUtils.h"
#include <processenv.h>
#include <synchapi.h>
using namespace content_analysis::sdk;
-MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock) {
- nsString cmdLineArguments;
- if (aToBlock && aToBlock[0] != 0) {
- cmdLineArguments.Append(L" --toblock=.*");
- cmdLineArguments.Append(aToBlock);
- cmdLineArguments.Append(L".*");
- }
- cmdLineArguments.Append(L" --user");
- cmdLineArguments.Append(L" --path=");
- nsString pipeName;
- GeneratePipeName(L"contentanalysissdk-gtest-", pipeName);
- cmdLineArguments.Append(pipeName);
- MozAgentInfo agentInfo;
- LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo);
- return agentInfo;
-}
-
TEST(ContentAnalysisAgent, TextShouldNotBeBlocked)
{
auto MozAgentInfo = LaunchAgentNormal(L"block");
@@ -49,10 +32,7 @@ TEST(ContentAnalysisAgent, TextShouldNotBeBlocked)
response.results().Get(0).status());
ASSERT_EQ(0, response.results().Get(0).triggered_rules_size());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisAgent, TextShouldBeBlocked)
@@ -75,10 +55,7 @@ TEST(ContentAnalysisAgent, TextShouldBeBlocked)
ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK,
response.results().Get(0).triggered_rules(0).action());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisAgent, FileShouldNotBeBlocked)
@@ -99,10 +76,7 @@ TEST(ContentAnalysisAgent, FileShouldNotBeBlocked)
response.results().Get(0).status());
ASSERT_EQ(0, response.results().Get(0).triggered_rules_size());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisAgent, FileShouldBeBlocked)
@@ -125,8 +99,5 @@ TEST(ContentAnalysisAgent, FileShouldBeBlocked)
ASSERT_EQ(ContentAnalysisResponse_Result_TriggeredRule_Action_BLOCK,
response.results().Get(0).triggered_rules(0).action());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp
index 7c944ed6e3..a120a82f7c 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisMisbehaving.cpp
@@ -8,7 +8,7 @@
#include "mozilla/Assertions.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "content_analysis/sdk/analysis_client.h"
-#include "TestContentAnalysisAgent.h"
+#include "TestContentAnalysisUtils.h"
#include <processenv.h>
#include <synchapi.h>
#include <windows.h>
@@ -70,10 +70,7 @@ TEST(ContentAnalysisMisbehaving, InvalidUtf8StringStartByteIsContinuationByte)
// or invalid memory access or something.
ASSERT_STREQ("\x80\x41\x41\x41", response.request_token().c_str());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving,
@@ -95,10 +92,7 @@ TEST(ContentAnalysisMisbehaving,
// or invalid memory access or something.
ASSERT_STREQ("\x41\xf0\x90\x8d", response.request_token().c_str());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, InvalidUtf8StringMultibyteSequenceTooShort)
@@ -119,10 +113,7 @@ TEST(ContentAnalysisMisbehaving, InvalidUtf8StringMultibyteSequenceTooShort)
// or invalid memory access or something.
ASSERT_STREQ("\xf0\x90\x8d\x41", response.request_token().c_str());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, InvalidUtf8StringDecodesToInvalidCodePoint)
@@ -143,10 +134,7 @@ TEST(ContentAnalysisMisbehaving, InvalidUtf8StringDecodesToInvalidCodePoint)
// or invalid memory access or something.
ASSERT_STREQ("\xf7\xbf\xbf\xbf", response.request_token().c_str());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, InvalidUtf8StringOverlongEncoding)
@@ -167,10 +155,7 @@ TEST(ContentAnalysisMisbehaving, InvalidUtf8StringOverlongEncoding)
// or invalid memory access or something.
ASSERT_STREQ("\xf0\x82\x82\xac", response.request_token().c_str());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, StringWithEmbeddedNull)
@@ -188,10 +173,7 @@ TEST(ContentAnalysisMisbehaving, StringWithEmbeddedNull)
std::string expected("\x41\x00\x41");
ASSERT_EQ(expected, response.request_token());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, ZeroResults)
@@ -208,10 +190,7 @@ TEST(ContentAnalysisMisbehaving, ZeroResults)
ASSERT_EQ(0, MozAgentInfo.client->Send(request, &response));
ASSERT_EQ(0, response.results().size());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, ResultWithInvalidStatus)
@@ -232,10 +211,7 @@ TEST(ContentAnalysisMisbehaving, ResultWithInvalidStatus)
// just make sure we can get the value without throwing
ASSERT_GE(static_cast<int>(response.results(0).status()), 0);
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageTruncatedInMiddleOfString)
@@ -252,10 +228,7 @@ TEST(ContentAnalysisMisbehaving, MessageTruncatedInMiddleOfString)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageWithInvalidWireType)
@@ -271,10 +244,7 @@ TEST(ContentAnalysisMisbehaving, MessageWithInvalidWireType)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageWithUnusedFieldNumber)
@@ -292,10 +262,7 @@ TEST(ContentAnalysisMisbehaving, MessageWithUnusedFieldNumber)
// just make sure we can get a value without throwing
ASSERT_STREQ("", response.request_token().c_str());
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageWithWrongStringWireType)
@@ -311,10 +278,7 @@ TEST(ContentAnalysisMisbehaving, MessageWithWrongStringWireType)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageWithZeroTag)
@@ -330,10 +294,7 @@ TEST(ContentAnalysisMisbehaving, MessageWithZeroTag)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageWithZeroFieldButNonzeroWireType)
@@ -350,10 +311,7 @@ TEST(ContentAnalysisMisbehaving, MessageWithZeroFieldButNonzeroWireType)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageWithGroupEnd)
@@ -370,10 +328,7 @@ TEST(ContentAnalysisMisbehaving, MessageWithGroupEnd)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageTruncatedInMiddleOfVarint)
@@ -390,10 +345,7 @@ TEST(ContentAnalysisMisbehaving, MessageTruncatedInMiddleOfVarint)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
TEST(ContentAnalysisMisbehaving, MessageTruncatedInMiddleOfTag)
@@ -409,8 +361,5 @@ TEST(ContentAnalysisMisbehaving, MessageTruncatedInMiddleOfTag)
// The response is an invalid serialization of protobuf, so this should fail
ASSERT_EQ(-1, MozAgentInfo.client->Send(request, &response));
- BOOL terminateResult =
- ::TerminateProcess(MozAgentInfo.processInfo.hProcess, 0);
- ASSERT_NE(FALSE, terminateResult)
- << "Failed to terminate content_analysis_sdk_agent process";
+ MozAgentInfo.TerminateProcess();
}
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp
index 0e14de6b81..8bcfe018ee 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.cpp
@@ -3,13 +3,35 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-#include "TestContentAnalysisAgent.h"
+#include "TestContentAnalysisUtils.h"
#include <combaseapi.h>
#include <pathcch.h>
#include <shlwapi.h>
#include <rpc.h>
#include <windows.h>
+MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock) {
+ nsString pipeName;
+ GeneratePipeName(L"contentanalysissdk-gtest-", pipeName);
+ return LaunchAgentNormal(aToBlock, pipeName);
+}
+
+MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock,
+ const nsString& pipeName) {
+ nsString cmdLineArguments;
+ if (aToBlock && aToBlock[0] != 0) {
+ cmdLineArguments.Append(L" --toblock=.*");
+ cmdLineArguments.Append(aToBlock);
+ cmdLineArguments.Append(L".*");
+ }
+ cmdLineArguments.Append(L" --user");
+ cmdLineArguments.Append(L" --path=");
+ cmdLineArguments.Append(pipeName);
+ MozAgentInfo agentInfo;
+ LaunchAgentWithCommandLineArguments(cmdLineArguments, pipeName, agentInfo);
+ return agentInfo;
+}
+
void GeneratePipeName(const wchar_t* prefix, nsString& pipeName) {
pipeName = u""_ns;
pipeName.Append(prefix);
diff --git a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.h
index 9e31036262..fd437de3f7 100644
--- a/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisAgent.h
+++ b/toolkit/components/contentanalysis/tests/gtest/TestContentAnalysisUtils.h
@@ -15,10 +15,18 @@
struct MozAgentInfo {
PROCESS_INFORMATION processInfo;
std::unique_ptr<content_analysis::sdk::Client> client;
+ void TerminateProcess() {
+ BOOL terminateResult = ::TerminateProcess(processInfo.hProcess, 0);
+ ASSERT_NE(FALSE, terminateResult)
+ << "Failed to terminate content_analysis_sdk_agent process";
+ }
};
void GeneratePipeName(const wchar_t* prefix, nsString& pipeName);
void LaunchAgentWithCommandLineArguments(const nsString& cmdLineArguments,
const nsString& pipeName,
MozAgentInfo& agentInfo);
+MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock);
+MozAgentInfo LaunchAgentNormal(const wchar_t* aToBlock,
+ const nsString& pipeName);
#endif