Skip to content

Commit 1660676

Browse files
committed
Bug 1879152 - Add confirmation dialog on quit when DLP requests are pending r=handyman,fluent-reviewers,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D201154
1 parent bde44fb commit 1660676

File tree

5 files changed

+155
-8
lines changed

5 files changed

+155
-8
lines changed

browser/components/contentanalysis/content/ContentAnalysis.sys.mjs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ class MapByTopBrowsingContext {
156156
* @returns {MapByTopBrowsingContext} this
157157
*/
158158
setEntry(aBrowsingContext, aValue) {
159+
if (!aValue.request) {
160+
console.error(
161+
"MapByTopBrowsingContext.setEntry() called with a value without a request!"
162+
);
163+
}
159164
let topEntry = this.#map.get(aBrowsingContext.top);
160165
if (!topEntry) {
161166
topEntry = new Map();
@@ -164,6 +169,16 @@ class MapByTopBrowsingContext {
164169
topEntry.set(aBrowsingContext, aValue);
165170
return this;
166171
}
172+
173+
getAllRequests() {
174+
let requests = [];
175+
this.#map.forEach(topEntry => {
176+
for (let entry of topEntry.values()) {
177+
requests.push(entry.request);
178+
}
179+
});
180+
return requests;
181+
}
167182
}
168183

169184
export const ContentAnalysis = {
@@ -196,7 +211,7 @@ export const ContentAnalysis = {
196211

197212
ChromeUtils.defineLazyGetter(this, "l10n", function () {
198213
return new Localization(
199-
["toolkit/contentanalysis/contentanalysis.ftl"],
214+
["branding/brand.ftl", "toolkit/contentanalysis/contentanalysis.ftl"],
200215
true
201216
);
202217
});
@@ -217,11 +232,54 @@ export const ContentAnalysis = {
217232
Services.obs.addObserver(this, "dlp-request-made");
218233
Services.obs.addObserver(this, "dlp-response");
219234
Services.obs.addObserver(this, "quit-application");
235+
Services.obs.addObserver(this, "quit-application-requested");
220236
},
221237

222238
// nsIObserver
223239
async observe(aSubj, aTopic, aData) {
224240
switch (aTopic) {
241+
case "quit-application-requested": {
242+
let pendingRequests =
243+
this.dlpBusyViewsByTopBrowsingContext.getAllRequests();
244+
if (pendingRequests.length) {
245+
let messageBody = this.l10n.formatValueSync(
246+
"contentanalysis-inprogress-quit-message"
247+
);
248+
messageBody = messageBody + "\n\n";
249+
for (const pendingRequest of pendingRequests) {
250+
let name = this._getResourceNameFromNameOrOperationType(
251+
this._getResourceNameOrOperationTypeFromRequest(
252+
pendingRequest,
253+
true
254+
)
255+
);
256+
messageBody = messageBody + name + "\n";
257+
}
258+
let buttonSelected = Services.prompt.confirmEx(
259+
null,
260+
this.l10n.formatValueSync("contentanalysis-inprogress-quit-title"),
261+
messageBody,
262+
Ci.nsIPromptService.BUTTON_POS_0 *
263+
Ci.nsIPromptService.BUTTON_TITLE_IS_STRING +
264+
Ci.nsIPromptService.BUTTON_POS_1 *
265+
Ci.nsIPromptService.BUTTON_TITLE_CANCEL +
266+
Ci.nsIPromptService.BUTTON_POS_0_DEFAULT,
267+
this.l10n.formatValueSync(
268+
"contentanalysis-inprogress-quit-yesbutton"
269+
),
270+
null,
271+
null,
272+
null,
273+
{ value: 0 }
274+
);
275+
if (buttonSelected === 0) {
276+
lazy.gContentAnalysis.cancelAllRequests();
277+
} else {
278+
aSubj.data = true;
279+
}
280+
}
281+
break;
282+
}
225283
case "quit-application": {
226284
this.uninitialize();
227285
break;
@@ -259,7 +317,7 @@ export const ContentAnalysis = {
259317
);
260318
}
261319
let resourceNameOrOperationType =
262-
this._getResourceNameOrOperationTypeFromRequest(request);
320+
this._getResourceNameOrOperationTypeFromRequest(request, false);
263321
this.requestTokenToRequestInfo.set(request.requestToken, {
264322
browsingContext,
265323
resourceNameOrOperationType,
@@ -273,8 +331,10 @@ export const ContentAnalysis = {
273331
resourceNameOrOperationType,
274332
browsingContext
275333
),
334+
request,
276335
});
277336
}, slowTimeoutMs),
337+
request,
278338
});
279339
}
280340
break;
@@ -335,6 +395,7 @@ export const ContentAnalysis = {
335395
args.requestToken,
336396
args.resourceNameOrOperationType
337397
),
398+
request: args.request,
338399
});
339400
}
340401
},
@@ -431,11 +492,32 @@ export const ContentAnalysis = {
431492
return nameOrOperationType.name;
432493
},
433494

434-
_getResourceNameOrOperationTypeFromRequest(aRequest) {
495+
/**
496+
* Gets a name or operation type from a request
497+
*
498+
* @param {object} aRequest The nsIContentAnalysisRequest
499+
* @param {boolean} aStandalone Whether the message is going to be used on its own
500+
* line. This is used to add more context to the message
501+
* if a file is being uploaded rather than just the name
502+
* of the file.
503+
* @returns {object} An object with either a name property that can be used as-is, or
504+
* an operationType property.
505+
*/
506+
_getResourceNameOrOperationTypeFromRequest(aRequest, aStandalone) {
435507
if (
436508
aRequest.operationTypeForDisplay ==
437509
Ci.nsIContentAnalysisRequest.eCustomDisplayString
438510
) {
511+
if (aStandalone) {
512+
return {
513+
name: this.l10n.formatValueSync(
514+
"contentanalysis-customdisplaystring-description",
515+
{
516+
filename: aRequest.operationDisplayString,
517+
}
518+
),
519+
};
520+
}
439521
return { name: aRequest.operationDisplayString };
440522
}
441523
return { operationType: aRequest.operationTypeForDisplay };

toolkit/components/contentanalysis/ContentAnalysis.cpp

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "mozilla/Logging.h"
1616
#include "mozilla/ScopeExit.h"
1717
#include "mozilla/Services.h"
18+
#include "mozilla/StaticMutex.h"
1819
#include "mozilla/StaticPrefs_browser.h"
1920
#include "nsAppRunner.h"
2021
#include "nsComponentManagerUtils.h"
@@ -283,6 +284,15 @@ nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
283284
return NS_OK;
284285
}
285286

287+
// Generate an ID that will be shared by all DLP requests.
288+
// Used to cancel all requests on Firefox shutdown.
289+
void ContentAnalysis::GenerateUserActionId() {
290+
nsID id = nsID::GenerateUUID();
291+
mUserActionId = nsPrintfCString("Firefox %s", id.ToString().get());
292+
}
293+
294+
nsCString ContentAnalysis::GetUserActionId() { return mUserActionId; }
295+
286296
static nsresult ConvertToProtobuf(
287297
nsIClientDownloadResource* aIn,
288298
content_analysis::sdk::ClientDownloadRequest_Resource* aOut) {
@@ -302,7 +312,8 @@ static nsresult ConvertToProtobuf(
302312
}
303313

304314
static nsresult ConvertToProtobuf(
305-
nsIContentAnalysisRequest* aIn,
315+
nsIContentAnalysisRequest* aIn, nsCString&& aUserActionId,
316+
int64_t aRequestCount,
306317
content_analysis::sdk::ContentAnalysisRequest* aOut) {
307318
uint32_t timeout = StaticPrefs::browser_contentanalysis_agent_timeout();
308319
aOut->set_expires_at(time(nullptr) + timeout);
@@ -319,6 +330,9 @@ static nsresult ConvertToProtobuf(
319330
NS_ENSURE_SUCCESS(rv, rv);
320331
aOut->set_request_token(requestToken.get(), requestToken.Length());
321332

333+
aOut->set_user_action_id(aUserActionId.get());
334+
aOut->set_user_action_requests_count(aRequestCount);
335+
322336
const std::string tag = "dlp"; // TODO:
323337
*aOut->add_tags() = tag;
324338

@@ -699,7 +713,9 @@ ContentAnalysis::ContentAnalysis()
699713
new ClientPromise::Private("ContentAnalysis::ContentAnalysis")),
700714
mClientCreationAttempted(false),
701715
mCallbackMap("ContentAnalysis::mCallbackMap"),
702-
mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") {}
716+
mWarnResponseDataMap("ContentAnalysis::mWarnResponseDataMap") {
717+
GenerateUserActionId();
718+
}
703719

704720
ContentAnalysis::~ContentAnalysis() {
705721
// Accessing mClientCreationAttempted so need to be on the main thread
@@ -768,7 +784,7 @@ ContentAnalysis::GetMightBeActive(bool* aMightBeActive) {
768784
nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken,
769785
nsresult aResult) {
770786
return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
771-
"ContentAnalysis::RunAnalyzeRequestTask::HandleResponse",
787+
"ContentAnalysis::CancelWithError",
772788
[aResult, aRequestToken = std::move(aRequestToken)] {
773789
RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
774790
if (!owner) {
@@ -816,6 +832,7 @@ RefPtr<ContentAnalysis> ContentAnalysis::GetContentAnalysisFromService() {
816832

817833
nsresult ContentAnalysis::RunAnalyzeRequestTask(
818834
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
835+
int64_t aRequestCount,
819836
const RefPtr<nsIContentAnalysisCallback>& aCallback) {
820837
nsresult rv = NS_ERROR_FAILURE;
821838
auto callbackCopy = aCallback;
@@ -827,7 +844,8 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
827844
});
828845

829846
content_analysis::sdk::ContentAnalysisRequest pbRequest;
830-
rv = ConvertToProtobuf(aRequest, &pbRequest);
847+
rv =
848+
ConvertToProtobuf(aRequest, GetUserActionId(), aRequestCount, &pbRequest);
831849
NS_ENSURE_SUCCESS(rv, rv);
832850

833851
nsCString requestToken;
@@ -1047,7 +1065,11 @@ ContentAnalysis::AnalyzeContentRequestCallback(
10471065
mozilla::services::GetObserverService();
10481066
obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
10491067

1050-
return RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, aCallback);
1068+
MOZ_ASSERT(NS_IsMainThread());
1069+
// since we're on the main thread, don't need to synchronize this
1070+
int64_t requestCount = ++mRequestCount;
1071+
return RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, requestCount,
1072+
aCallback);
10511073
}
10521074

10531075
NS_IMETHODIMP
@@ -1075,6 +1097,33 @@ ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) {
10751097
return NS_OK;
10761098
}
10771099

1100+
NS_IMETHODIMP
1101+
ContentAnalysis::CancelAllRequests() {
1102+
mCaClientPromise->Then(
1103+
GetCurrentSerialEventTarget(), __func__,
1104+
[&](std::shared_ptr<content_analysis::sdk::Client> client) {
1105+
auto owner = GetContentAnalysisFromService();
1106+
if (!owner) {
1107+
// May be shutting down
1108+
return;
1109+
}
1110+
if (!client) {
1111+
LOGE("CancelAllRequests got a null client");
1112+
return;
1113+
}
1114+
content_analysis::sdk::ContentAnalysisCancelRequests requests;
1115+
requests.set_user_action_id(owner->GetUserActionId().get());
1116+
int err = client->CancelRequests(requests);
1117+
if (err != 0) {
1118+
LOGE("CancelAllRequests got error %d", err);
1119+
} else {
1120+
LOGD("CancelAllRequests did cancelling of requests");
1121+
}
1122+
},
1123+
[&](nsresult rv) { LOGE("CancelAllRequests failed to get the client"); });
1124+
return NS_OK;
1125+
}
1126+
10781127
NS_IMETHODIMP
10791128
ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken,
10801129
bool aAllowContent) {

toolkit/components/contentanalysis/ContentAnalysis.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
9292
NS_DECL_NSICONTENTANALYSIS
9393

9494
ContentAnalysis();
95+
nsCString GetUserActionId();
9596

9697
private:
9798
~ContentAnalysis();
@@ -103,11 +104,13 @@ class ContentAnalysis final : public nsIContentAnalysis {
103104
bool aIsPerUser);
104105
nsresult RunAnalyzeRequestTask(
105106
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
107+
int64_t aRequestCount,
106108
const RefPtr<nsIContentAnalysisCallback>& aCallback);
107109
nsresult RunAcknowledgeTask(
108110
nsIContentAnalysisAcknowledgement* aAcknowledgement,
109111
const nsACString& aRequestToken);
110112
nsresult CancelWithError(nsCString aRequestToken, nsresult aResult);
113+
void GenerateUserActionId();
111114
static RefPtr<ContentAnalysis> GetContentAnalysisFromService();
112115
static void DoAnalyzeRequest(
113116
nsCString aRequestToken,
@@ -117,6 +120,8 @@ class ContentAnalysis final : public nsIContentAnalysis {
117120
using ClientPromise =
118121
MozPromise<std::shared_ptr<content_analysis::sdk::Client>, nsresult,
119122
false>;
123+
nsCString mUserActionId;
124+
int64_t mRequestCount = 0;
120125
RefPtr<ClientPromise::Private> mCaClientPromise;
121126
// Only accessed from the main thread
122127
bool mClientCreationAttempted;

toolkit/components/contentanalysis/nsIContentAnalysis.idl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,4 +236,9 @@ interface nsIContentAnalysis : nsISupports
236236
* whether the user wants to allow the request to go through.
237237
*/
238238
void respondToWarnDialog(in ACString aRequestToken, in bool aAllowContent);
239+
240+
/**
241+
* Cancels all outstanding DLP requests. Used on shutdown.
242+
*/
243+
void cancelAllRequests();
239244
};

toolkit/locales/en-US/toolkit/contentanalysis/contentanalysis.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ contentanalysis-slow-agent-dialog-body-clipboard = { $agent } is reviewing what
2222
contentanalysis-slow-agent-dialog-body-dropped-text = { $agent } is reviewing the text you dropped against your organization’s data policies. This may take a moment.
2323
contentanalysis-operationtype-clipboard = clipboard
2424
contentanalysis-operationtype-dropped-text = dropped text
25+
# $filename - The filename associated with the request, such as "aFile.txt"
26+
contentanalysis-customdisplaystring-description = upload of "{ $filename }"
2527
2628
contentanalysis-warndialogtitle = This content may be unsafe
2729
@@ -42,3 +44,7 @@ contentanalysis-block-message = Your organization uses data-loss prevention soft
4244
# Variables:
4345
# $content - Description of the content being blocked, such as "clipboard" or "aFile.txt"
4446
contentanalysis-error-message = An error occurred in communicating with the data-loss prevention software. Transfer denied for resource: { $content }.
47+
48+
contentanalysis-inprogress-quit-title = Quit { -brand-shorter-name }?
49+
contentanalysis-inprogress-quit-message = Several actions are in progress. If you quit { -brand-shorter-name }, these actions will not be completed.
50+
contentanalysis-inprogress-quit-yesbutton = Yes, quit

0 commit comments

Comments
 (0)