mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-03 17:58:55 +02:00
Bug 1811076: Part 5 - Add UI notifications for content analysis results r=nika,Gijs,fluent-reviewers,flod
Connects content analysis checks with the tab that their messages to the user should appear on. Adds notifications for the CA messages. Differential Revision: https://phabricator.services.mozilla.com/D191784
This commit is contained in:
parent
287dacd96f
commit
14de68880a
9 changed files with 790 additions and 32 deletions
|
|
@ -24,6 +24,7 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||||
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
|
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
|
||||||
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
||||||
Color: "resource://gre/modules/Color.sys.mjs",
|
Color: "resource://gre/modules/Color.sys.mjs",
|
||||||
|
ContentAnalysis: "resource:///modules/ContentAnalysis.sys.mjs",
|
||||||
ContextualIdentityService:
|
ContextualIdentityService:
|
||||||
"resource://gre/modules/ContextualIdentityService.sys.mjs",
|
"resource://gre/modules/ContextualIdentityService.sys.mjs",
|
||||||
CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
|
CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
|
||||||
|
|
@ -1871,6 +1872,7 @@ var gBrowserInit = {
|
||||||
BrowserOffline.init();
|
BrowserOffline.init();
|
||||||
CanvasPermissionPromptHelper.init();
|
CanvasPermissionPromptHelper.init();
|
||||||
WebAuthnPromptHelper.init();
|
WebAuthnPromptHelper.init();
|
||||||
|
ContentAnalysis.initialize();
|
||||||
|
|
||||||
// Initialize the full zoom setting.
|
// Initialize the full zoom setting.
|
||||||
// We do this before the session restore service gets initialized so we can
|
// We do this before the session restore service gets initialized so we can
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,582 @@
|
||||||
|
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains elements of the Content Analysis UI, which are integrated into
|
||||||
|
* various browser behaviors (uploading, downloading, printing, etc) that
|
||||||
|
* require content analysis to be done.
|
||||||
|
* The content analysis itself is done by the clients of this script, who
|
||||||
|
* use nsIContentAnalysis to talk to the external CA system.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||||
|
|
||||||
|
const lazy = {};
|
||||||
|
|
||||||
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
|
clearTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||||
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||||
|
});
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyPreferenceGetter(
|
||||||
|
lazy,
|
||||||
|
"silentNotifications",
|
||||||
|
"browser.contentanalysis.silent_notifications",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that groups browsing contexts by their top-level one.
|
||||||
|
* This is necessary because if there may be a subframe that
|
||||||
|
* is showing a "DLP request busy" dialog when another subframe
|
||||||
|
* (other the outer frame) wants to show one. This class makes it
|
||||||
|
* convenient to find if another frame with the same top browsing
|
||||||
|
* context is currently showing a dialog, and also to find if there
|
||||||
|
* are any pending dialogs to show when one closes.
|
||||||
|
*/
|
||||||
|
class MapByTopBrowsingContext {
|
||||||
|
#map;
|
||||||
|
constructor() {
|
||||||
|
this.#map = new Map();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets any existing data associated with the browsing context
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} aBrowsingContext the browsing context to search for
|
||||||
|
* @returns {object | undefined} the existing data, or `undefined` if there is none
|
||||||
|
*/
|
||||||
|
getEntry(aBrowsingContext) {
|
||||||
|
const topEntry = this.#map.get(aBrowsingContext.top);
|
||||||
|
if (!topEntry) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return topEntry.get(aBrowsingContext);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns whether the browsing context has any data associated with it
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} aBrowsingContext the browsing context to search for
|
||||||
|
* @returns {boolean} Whether the browsing context has any associated data
|
||||||
|
*/
|
||||||
|
hasEntry(aBrowsingContext) {
|
||||||
|
const topEntry = this.#map.get(aBrowsingContext.top);
|
||||||
|
if (!topEntry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return topEntry.has(aBrowsingContext);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Whether the tab containing the browsing context has a dialog
|
||||||
|
* currently showing
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} aBrowsingContext the browsing context to search for
|
||||||
|
* @returns {boolean} whether the tab has a dialog currently showing
|
||||||
|
*/
|
||||||
|
hasEntryDisplayingNotification(aBrowsingContext) {
|
||||||
|
const topEntry = this.#map.get(aBrowsingContext.top);
|
||||||
|
if (!topEntry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const otherEntry in topEntry.values()) {
|
||||||
|
if (otherEntry.notification?.dialogBrowsingContext) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Gets another browsing context in the same tab that has pending "DLP busy" dialog
|
||||||
|
* info to show, if any.
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} aBrowsingContext the browsing context to search for
|
||||||
|
* @returns {BrowsingContext} Another browsing context in the same tab that has pending "DLP busy" dialog info, or `undefined` if there aren't any.
|
||||||
|
*/
|
||||||
|
getBrowsingContextWithPendingNotification(aBrowsingContext) {
|
||||||
|
const topEntry = this.#map.get(aBrowsingContext.top);
|
||||||
|
if (!topEntry) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (aBrowsingContext.top.isDiscarded) {
|
||||||
|
// The top-level tab has already been closed, so remove
|
||||||
|
// the top-level entry and return there are no pending dialogs.
|
||||||
|
this.#map.delete(aBrowsingContext.top);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
for (const otherContext in topEntry.keys()) {
|
||||||
|
if (
|
||||||
|
topEntry.get(otherContext).notification?.dialogBrowsingContextArgs &&
|
||||||
|
otherContext !== aBrowsingContext
|
||||||
|
) {
|
||||||
|
return otherContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Deletes the entry for the browsing context, if any
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} aBrowsingContext the browsing context to delete
|
||||||
|
* @returns {boolean} Whether an entry was deleted or not
|
||||||
|
*/
|
||||||
|
deleteEntry(aBrowsingContext) {
|
||||||
|
const topEntry = this.#map.get(aBrowsingContext.top);
|
||||||
|
if (!topEntry) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const toReturn = topEntry.delete(aBrowsingContext);
|
||||||
|
if (!topEntry.size || aBrowsingContext.top.isDiscarded) {
|
||||||
|
// Either the inner Map is now empty, or the whole tab
|
||||||
|
// has been closed. Either way, remove the top-level entry.
|
||||||
|
this.#map.delete(aBrowsingContext.top);
|
||||||
|
}
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets the associated data for the browsing context
|
||||||
|
*
|
||||||
|
* @param {BrowsingContext} aBrowsingContext the browsing context to set the data for
|
||||||
|
* @param {object} aValue the data to associated with the browsing context
|
||||||
|
* @returns {MapByTopBrowsingContext} this
|
||||||
|
*/
|
||||||
|
setEntry(aBrowsingContext, aValue) {
|
||||||
|
let topEntry = this.#map.get(aBrowsingContext.top);
|
||||||
|
if (!topEntry) {
|
||||||
|
topEntry = new Map();
|
||||||
|
this.#map.set(aBrowsingContext.top, topEntry);
|
||||||
|
}
|
||||||
|
topEntry.set(aBrowsingContext, aValue);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ContentAnalysis = {
|
||||||
|
_SHOW_NOTIFICATIONS: true,
|
||||||
|
|
||||||
|
_SHOW_DIALOGS: false,
|
||||||
|
|
||||||
|
_SLOW_DLP_NOTIFICATION_BLOCKING_TIMEOUT_MS: 250,
|
||||||
|
|
||||||
|
_SLOW_DLP_NOTIFICATION_NONBLOCKING_TIMEOUT_MS: 3 * 1000,
|
||||||
|
|
||||||
|
_RESULT_NOTIFICATION_TIMEOUT_MS: 5 * 60 * 1000, // 5 min
|
||||||
|
|
||||||
|
_RESULT_NOTIFICATION_FAST_TIMEOUT_MS: 60 * 1000, // 1 min
|
||||||
|
|
||||||
|
isInitialized: false,
|
||||||
|
|
||||||
|
dlpBusyViewsByTopBrowsingContext: new MapByTopBrowsingContext(),
|
||||||
|
|
||||||
|
requestTokenToRequestInfo: new Map(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers for various messages/events that will indicate the
|
||||||
|
* need for communicating something to the user.
|
||||||
|
*/
|
||||||
|
initialize() {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
this.isInitialized = true;
|
||||||
|
this.initializeDownloadCA();
|
||||||
|
|
||||||
|
ChromeUtils.defineLazyGetter(this, "l10n", function () {
|
||||||
|
return new Localization(
|
||||||
|
["toolkit/contentanalysis/contentanalysis.ftl"],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async uninitialize() {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
this.isInitialized = false;
|
||||||
|
this.requestTokenToRequestInfo.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register UI for file download CA events.
|
||||||
|
*/
|
||||||
|
async initializeDownloadCA() {
|
||||||
|
Services.obs.addObserver(this, "dlp-request-made");
|
||||||
|
Services.obs.addObserver(this, "dlp-response");
|
||||||
|
Services.obs.addObserver(this, "quit-application");
|
||||||
|
},
|
||||||
|
|
||||||
|
// nsIObserver
|
||||||
|
async observe(aSubj, aTopic, aData) {
|
||||||
|
switch (aTopic) {
|
||||||
|
case "quit-application": {
|
||||||
|
this.uninitialize();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "dlp-request-made":
|
||||||
|
{
|
||||||
|
const request = aSubj;
|
||||||
|
if (!request) {
|
||||||
|
console.error(
|
||||||
|
"Showing in-browser Content Analysis notification but no request was passed"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const operation = request.analysisType;
|
||||||
|
// For operations that block browser interaction, show the "slow content analysis"
|
||||||
|
// dialog faster
|
||||||
|
let slowTimeoutMs = this._shouldShowBlockingNotification(operation)
|
||||||
|
? this._SLOW_DLP_NOTIFICATION_BLOCKING_TIMEOUT_MS
|
||||||
|
: this._SLOW_DLP_NOTIFICATION_NONBLOCKING_TIMEOUT_MS;
|
||||||
|
let browsingContext = request.windowGlobalParent?.browsingContext;
|
||||||
|
if (!browsingContext) {
|
||||||
|
throw new Error(
|
||||||
|
"Got dlp-request-made message but couldn't find a browsingContext!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timer that, when it expires,
|
||||||
|
// presents a "slow CA check" message.
|
||||||
|
// Note that there should only be one DLP request
|
||||||
|
// at a time per browsingContext (since we block the UI and
|
||||||
|
// the content process waits synchronously for the result).
|
||||||
|
if (this.dlpBusyViewsByTopBrowsingContext.hasEntry(browsingContext)) {
|
||||||
|
throw new Error(
|
||||||
|
"Got dlp-request-made message for a browsingContext that already has a busy view!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let resourceNameOrL10NId =
|
||||||
|
this._getResourceNameOrL10NIdFromRequest(request);
|
||||||
|
this.requestTokenToRequestInfo.set(request.requestToken, {
|
||||||
|
browsingContext,
|
||||||
|
resourceNameOrL10NId,
|
||||||
|
});
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.setEntry(browsingContext, {
|
||||||
|
timer: lazy.setTimeout(() => {
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.setEntry(browsingContext, {
|
||||||
|
notification: this._showSlowCAMessage(
|
||||||
|
operation,
|
||||||
|
request,
|
||||||
|
resourceNameOrL10NId,
|
||||||
|
browsingContext
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}, slowTimeoutMs),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "dlp-response":
|
||||||
|
const request = aSubj;
|
||||||
|
// Cancels timer or slow message UI,
|
||||||
|
// if present, and possibly presents the CA verdict.
|
||||||
|
if (!request) {
|
||||||
|
throw new Error("Got dlp-response message but no request was passed");
|
||||||
|
}
|
||||||
|
|
||||||
|
let windowAndResourceNameOrL10NId = this.requestTokenToRequestInfo.get(
|
||||||
|
request.requestToken
|
||||||
|
);
|
||||||
|
if (!windowAndResourceNameOrL10NId) {
|
||||||
|
// Perhaps this was cancelled just before the response came in from the
|
||||||
|
// DLP agent.
|
||||||
|
console.warn(
|
||||||
|
`Got dlp-response message with unknown token ${request.requestToken}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.requestTokenToRequestInfo.delete(request.requestToken);
|
||||||
|
let dlpBusyView = this.dlpBusyViewsByTopBrowsingContext.getEntry(
|
||||||
|
windowAndResourceNameOrL10NId.browsingContext
|
||||||
|
);
|
||||||
|
if (dlpBusyView) {
|
||||||
|
this._disconnectFromView(dlpBusyView);
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.deleteEntry(
|
||||||
|
windowAndResourceNameOrL10NId.browsingContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const responseResult =
|
||||||
|
request?.action ?? Ci.nsIContentAnalysisResponse.ACTION_UNSPECIFIED;
|
||||||
|
this._showCAResult(
|
||||||
|
windowAndResourceNameOrL10NId.resourceNameOrL10NId,
|
||||||
|
windowAndResourceNameOrL10NId.browsingContext,
|
||||||
|
request.requestToken,
|
||||||
|
responseResult
|
||||||
|
);
|
||||||
|
this._showAnotherPendingDialog(
|
||||||
|
windowAndResourceNameOrL10NId.browsingContext
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_showAnotherPendingDialog(aBrowsingContext) {
|
||||||
|
const otherBrowsingContext =
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.getBrowsingContextWithPendingNotification(
|
||||||
|
aBrowsingContext
|
||||||
|
);
|
||||||
|
if (otherBrowsingContext) {
|
||||||
|
const args =
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.getEntry(otherBrowsingContext);
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.setEntry(otherBrowsingContext, {
|
||||||
|
notification: this._showSlowCABlockingMessage(
|
||||||
|
otherBrowsingContext,
|
||||||
|
args.requestToken,
|
||||||
|
args.resourceNameOrL10NId
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_disconnectFromView(caView) {
|
||||||
|
if (!caView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (caView.timer) {
|
||||||
|
lazy.clearTimeout(caView.timer);
|
||||||
|
} else if (caView.notification) {
|
||||||
|
if (caView.notification.close) {
|
||||||
|
// native notification
|
||||||
|
caView.notification.close();
|
||||||
|
} else if (caView.notification.dialogBrowsingContext) {
|
||||||
|
// in-browser notification
|
||||||
|
let browser =
|
||||||
|
caView.notification.dialogBrowsingContext.top.embedderElement;
|
||||||
|
// browser will be null if the tab was closed
|
||||||
|
let win = browser?.ownerGlobal;
|
||||||
|
if (win) {
|
||||||
|
let dialogBox = win.gBrowser.getTabDialogBox(browser);
|
||||||
|
// Don't close any content-modal dialogs, because we could be doing
|
||||||
|
// content analysis on something like a prompt() call.
|
||||||
|
dialogBox.getTabDialogManager().abortDialogs();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"Unexpected content analysis notification - can't close it!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_showMessage(aMessage, aBrowsingContext, aTimeout = 0) {
|
||||||
|
if (this._SHOW_DIALOGS) {
|
||||||
|
Services.prompt.asyncAlert(
|
||||||
|
aBrowsingContext,
|
||||||
|
Ci.nsIPrompt.MODAL_TYPE_WINDOW,
|
||||||
|
this.l10n.formatValueSync("contentanalysis-alert-title"),
|
||||||
|
aMessage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._SHOW_NOTIFICATIONS) {
|
||||||
|
const notification = new aBrowsingContext.topChromeWindow.Notification(
|
||||||
|
this.l10n.formatValueSync("contentanalysis-notification-title"),
|
||||||
|
{
|
||||||
|
body: aMessage,
|
||||||
|
silent: lazy.silentNotifications,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (aTimeout != 0) {
|
||||||
|
lazy.setTimeout(() => {
|
||||||
|
notification.close();
|
||||||
|
}, aTimeout);
|
||||||
|
}
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
_shouldShowBlockingNotification(aOperation) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
// This function also transforms the nameOrL10NId so we won't have to
|
||||||
|
// look it up again.
|
||||||
|
_getResourceNameFromNameOrL10NId(nameOrL10NId) {
|
||||||
|
if (nameOrL10NId.name) {
|
||||||
|
return nameOrL10NId.name;
|
||||||
|
}
|
||||||
|
nameOrL10NId.name = this.l10n.formatValueSync(nameOrL10NId.l10nId);
|
||||||
|
return nameOrL10NId.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getResourceNameOrL10NIdFromRequest(aRequest) {
|
||||||
|
if (
|
||||||
|
aRequest.operationTypeForDisplay ==
|
||||||
|
Ci.nsIContentAnalysisRequest.OPERATION_CUSTOMDISPLAYSTRING
|
||||||
|
) {
|
||||||
|
return { name: aRequest.operationDisplayString };
|
||||||
|
}
|
||||||
|
let l10nId;
|
||||||
|
switch (aRequest.operationTypeForDisplay) {
|
||||||
|
case Ci.nsIContentAnalysisRequest.OPERATION_CLIPBOARD:
|
||||||
|
l10nId = "contentanalysis-operationtype-clipboard";
|
||||||
|
break;
|
||||||
|
case Ci.nsIContentAnalysisRequest.OPERATION_DROPPEDTEXT:
|
||||||
|
l10nId = "contentanalysis-operationtype-dropped-text";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!l10nId) {
|
||||||
|
console.error(
|
||||||
|
"Unknown operationTypeForDisplay: " + aRequest.operationTypeForDisplay
|
||||||
|
);
|
||||||
|
return { name: "" };
|
||||||
|
}
|
||||||
|
return { l10nId };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a message to the user to indicate that a CA request is taking
|
||||||
|
* a long time.
|
||||||
|
*/
|
||||||
|
_showSlowCAMessage(
|
||||||
|
aOperation,
|
||||||
|
aRequest,
|
||||||
|
aResourceNameOrL10NId,
|
||||||
|
aBrowsingContext
|
||||||
|
) {
|
||||||
|
if (!this._shouldShowBlockingNotification(aOperation)) {
|
||||||
|
return this._showMessage(
|
||||||
|
this.l10n.formatValueSync("contentanalysis-slow-agent-notification", {
|
||||||
|
content: this._getResourceNameFromNameOrL10NId(aResourceNameOrL10NId),
|
||||||
|
}),
|
||||||
|
aBrowsingContext
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!aRequest) {
|
||||||
|
throw new Error(
|
||||||
|
"Showing in-browser Content Analysis notification but no request was passed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.hasEntryDisplayingNotification(
|
||||||
|
aBrowsingContext
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// This tab already has a frame displaying a "DLP in progress" message, so we can't
|
||||||
|
// show another one right now. Record the arguments we will need to show another
|
||||||
|
// "DLP in progress" message when the existing message goes away.
|
||||||
|
return {
|
||||||
|
requestToken: aRequest.requestToken,
|
||||||
|
dialogBrowsingContextArgs: {
|
||||||
|
resourceNameOrL10NId: aResourceNameOrL10NId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._showSlowCABlockingMessage(
|
||||||
|
aBrowsingContext,
|
||||||
|
aRequest.requestToken,
|
||||||
|
aResourceNameOrL10NId
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
_showSlowCABlockingMessage(
|
||||||
|
aBrowsingContext,
|
||||||
|
aRequestToken,
|
||||||
|
aResourceNameOrL10NId
|
||||||
|
) {
|
||||||
|
let promise = Services.prompt.asyncConfirmEx(
|
||||||
|
aBrowsingContext,
|
||||||
|
Ci.nsIPromptService.MODAL_TYPE_TAB,
|
||||||
|
this.l10n.formatValueSync("contentanalysis-slow-agent-dialog-title"),
|
||||||
|
this.l10n.formatValueSync("contentanalysis-slow-agent-dialog-body", {
|
||||||
|
content: this._getResourceNameFromNameOrL10NId(aResourceNameOrL10NId),
|
||||||
|
}),
|
||||||
|
Ci.nsIPromptService.BUTTON_POS_0 *
|
||||||
|
Ci.nsIPromptService.BUTTON_TITLE_CANCEL +
|
||||||
|
Ci.nsIPromptService.SHOW_SPINNER,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
promise
|
||||||
|
.catch(() => {
|
||||||
|
// need a catch clause to avoid an unhandled JS exception
|
||||||
|
// when we programmatically close the dialog.
|
||||||
|
// Since this only happens when we are programmatically closing
|
||||||
|
// the dialog, no need to log the exception.
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// This is also be called if the tab/window is closed while a request is in progress,
|
||||||
|
// in which case we need to cancel the request.
|
||||||
|
if (this.requestTokenToRequestInfo.delete(aRequestToken)) {
|
||||||
|
let dlpBusyView =
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.getEntry(aBrowsingContext);
|
||||||
|
if (dlpBusyView) {
|
||||||
|
this._disconnectFromView(dlpBusyView);
|
||||||
|
this.dlpBusyViewsByTopBrowsingContext.deleteEntry(aBrowsingContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
requestToken: aRequestToken,
|
||||||
|
dialogBrowsingContext: aBrowsingContext,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a message to the user to indicate the result of a CA request.
|
||||||
|
*
|
||||||
|
* @returns {object} a notification object (if shown)
|
||||||
|
*/
|
||||||
|
_showCAResult(
|
||||||
|
aResourceNameOrL10NId,
|
||||||
|
aBrowsingContext,
|
||||||
|
aRequestToken,
|
||||||
|
aCAResult
|
||||||
|
) {
|
||||||
|
let message = null;
|
||||||
|
let timeoutMs = 0;
|
||||||
|
|
||||||
|
switch (aCAResult) {
|
||||||
|
case Ci.nsIContentAnalysisResponse.ALLOW:
|
||||||
|
// We don't need to show anything
|
||||||
|
return null;
|
||||||
|
case Ci.nsIContentAnalysisResponse.REPORT_ONLY:
|
||||||
|
message = this.l10n.formatValueSync(
|
||||||
|
"contentanalysis-genericresponse-message",
|
||||||
|
{
|
||||||
|
content: this._getResourceNameFromNameOrL10NId(
|
||||||
|
aResourceNameOrL10NId
|
||||||
|
),
|
||||||
|
response: "REPORT_ONLY",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
timeoutMs = this._RESULT_NOTIFICATION_FAST_TIMEOUT_MS;
|
||||||
|
break;
|
||||||
|
case Ci.nsIContentAnalysisResponse.WARN:
|
||||||
|
message = this.l10n.formatValueSync(
|
||||||
|
"contentanalysis-genericresponse-message",
|
||||||
|
{
|
||||||
|
content: this._getResourceNameFromNameOrL10NId(
|
||||||
|
aResourceNameOrL10NId
|
||||||
|
),
|
||||||
|
response: "WARN",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
timeoutMs = this._RESULT_NOTIFICATION_TIMEOUT_MS;
|
||||||
|
break;
|
||||||
|
case Ci.nsIContentAnalysisResponse.BLOCK:
|
||||||
|
message = this.l10n.formatValueSync("contentanalysis-block-message", {
|
||||||
|
content: this._getResourceNameFromNameOrL10NId(aResourceNameOrL10NId),
|
||||||
|
});
|
||||||
|
timeoutMs = this._RESULT_NOTIFICATION_TIMEOUT_MS;
|
||||||
|
break;
|
||||||
|
case Ci.nsIContentAnalysisResponse.ACTION_UNSPECIFIED:
|
||||||
|
message = this.l10n.formatValueSync("contentanalysis-error-message", {
|
||||||
|
content: this._getResourceNameFromNameOrL10NId(aResourceNameOrL10NId),
|
||||||
|
});
|
||||||
|
timeoutMs = this._RESULT_NOTIFICATION_TIMEOUT_MS;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Unexpected CA result value: " + aCAResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._showMessage(message, aBrowsingContext, timeoutMs);
|
||||||
|
},
|
||||||
|
};
|
||||||
12
browser/components/contentanalysis/moz.build
Normal file
12
browser/components/contentanalysis/moz.build
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||||
|
# vim: set filetype=python:
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
with Files("**"):
|
||||||
|
BUG_COMPONENT = ("Toolkit", "General")
|
||||||
|
|
||||||
|
EXTRA_JS_MODULES += [
|
||||||
|
"content/ContentAnalysis.sys.mjs",
|
||||||
|
]
|
||||||
|
|
@ -30,6 +30,7 @@ DIRS += [
|
||||||
"about",
|
"about",
|
||||||
"aboutlogins",
|
"aboutlogins",
|
||||||
"attribution",
|
"attribution",
|
||||||
|
"contentanalysis",
|
||||||
"contextualidentity",
|
"contextualidentity",
|
||||||
"customizableui",
|
"customizableui",
|
||||||
"doh",
|
"doh",
|
||||||
|
|
|
||||||
|
|
@ -1145,6 +1145,12 @@
|
||||||
value: "path_user"
|
value: "path_user"
|
||||||
mirror: never
|
mirror: never
|
||||||
|
|
||||||
|
# Should CA ignore the system setting and use silent notifications?
|
||||||
|
- name: browser.contentanalysis.silent_notifications
|
||||||
|
type: bool
|
||||||
|
value: false
|
||||||
|
mirror: always
|
||||||
|
|
||||||
# Content blocking for Enhanced Tracking Protection
|
# Content blocking for Enhanced Tracking Protection
|
||||||
- name: browser.contentblocking.database.enabled
|
- name: browser.contentblocking.database.enabled
|
||||||
type: bool
|
type: bool
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,15 @@
|
||||||
#include "mozilla/dom/Promise.h"
|
#include "mozilla/dom/Promise.h"
|
||||||
#include "mozilla/Logging.h"
|
#include "mozilla/Logging.h"
|
||||||
#include "mozilla/ScopeExit.h"
|
#include "mozilla/ScopeExit.h"
|
||||||
|
#include "mozilla/Services.h"
|
||||||
#include "mozilla/StaticPrefs_browser.h"
|
#include "mozilla/StaticPrefs_browser.h"
|
||||||
#include "nsAppRunner.h"
|
#include "nsAppRunner.h"
|
||||||
|
#include "nsComponentManagerUtils.h"
|
||||||
|
#include "nsIClassInfoImpl.h"
|
||||||
|
#include "nsIFile.h"
|
||||||
#include "nsIGlobalObject.h"
|
#include "nsIGlobalObject.h"
|
||||||
|
#include "nsIObserverService.h"
|
||||||
|
#include "ScopedNSSTypes.h"
|
||||||
#include "xpcpublic.h"
|
#include "xpcpublic.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -47,12 +53,19 @@ nsresult MakePromise(JSContext* aCx, RefPtr<mozilla::dom::Promise>* aPromise) {
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GenerateRequestToken() {
|
nsCString GenerateRequestToken() {
|
||||||
static std::atomic<uint32_t> count = 0;
|
nsID id = nsID::GenerateUUID();
|
||||||
uint32_t tokenValue = count.fetch_add(1, std::memory_order_relaxed);
|
return nsCString(id.ToString().get());
|
||||||
std::stringstream stm;
|
}
|
||||||
stm << std::hex << base::GetCurrentProcId() << "-" << tokenValue;
|
|
||||||
return stm.str();
|
static nsresult GetFileDisplayName(const nsString& aFilePath,
|
||||||
|
nsString& aFileDisplayName) {
|
||||||
|
nsresult rv;
|
||||||
|
nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1", &rv);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
rv = file->InitWithPath(aFilePath);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
return file->GetDisplayName(aFileDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
@ -105,6 +118,32 @@ ContentAnalysisRequest::GetResources(
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ContentAnalysisRequest::GetRequestToken(nsACString& aRequestToken) {
|
||||||
|
aRequestToken = mRequestToken;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ContentAnalysisRequest::GetOperationTypeForDisplay(uint32_t* aOperationType) {
|
||||||
|
*aOperationType = mOperationTypeForDisplay;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ContentAnalysisRequest::GetOperationDisplayString(
|
||||||
|
nsAString& aOperationDisplayString) {
|
||||||
|
aOperationDisplayString = mOperationDisplayString;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ContentAnalysisRequest::GetWindowGlobalParent(
|
||||||
|
dom::WindowGlobalParent** aWindowGlobalParent) {
|
||||||
|
NS_IF_ADDREF(*aWindowGlobalParent = mWindowGlobalParent);
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
StaticDataMutex<UniquePtr<content_analysis::sdk::Client>>
|
StaticDataMutex<UniquePtr<content_analysis::sdk::Client>>
|
||||||
ContentAnalysis::sCaClient("ContentAnalysisClient");
|
ContentAnalysis::sCaClient("ContentAnalysisClient");
|
||||||
|
|
@ -126,19 +165,29 @@ nsresult ContentAnalysis::EnsureContentAnalysisClient() {
|
||||||
return caClient ? NS_OK : NS_ERROR_NOT_AVAILABLE;
|
return caClient ? NS_OK : NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentAnalysisRequest::ContentAnalysisRequest(unsigned long aAnalysisType,
|
ContentAnalysisRequest::ContentAnalysisRequest(
|
||||||
nsString&& aString,
|
unsigned long aAnalysisType, nsString&& aString, bool aStringIsFilePath,
|
||||||
bool aStringIsFilePath,
|
nsCString&& aSha256Digest, nsString&& aUrl, unsigned long aResourceNameType,
|
||||||
nsCString&& aSha256Digest,
|
dom::WindowGlobalParent* aWindowGlobalParent)
|
||||||
nsString&& aUrl)
|
|
||||||
: mAnalysisType(aAnalysisType),
|
: mAnalysisType(aAnalysisType),
|
||||||
mUrl(std::move(aUrl)),
|
mUrl(std::move(aUrl)),
|
||||||
mSha256Digest(std::move(aSha256Digest)) {
|
mSha256Digest(std::move(aSha256Digest)),
|
||||||
|
mWindowGlobalParent(aWindowGlobalParent) {
|
||||||
if (aStringIsFilePath) {
|
if (aStringIsFilePath) {
|
||||||
mFilePath = std::move(aString);
|
mFilePath = std::move(aString);
|
||||||
} else {
|
} else {
|
||||||
mTextContent = std::move(aString);
|
mTextContent = std::move(aString);
|
||||||
}
|
}
|
||||||
|
mOperationTypeForDisplay = aResourceNameType;
|
||||||
|
if (mOperationTypeForDisplay == OPERATION_CUSTOMDISPLAYSTRING) {
|
||||||
|
MOZ_ASSERT(aStringIsFilePath);
|
||||||
|
nsresult rv = GetFileDisplayName(mFilePath, mOperationDisplayString);
|
||||||
|
if (NS_FAILED(rv)) {
|
||||||
|
mOperationDisplayString = u"file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mRequestToken = GenerateRequestToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
static nsresult ConvertToProtobuf(
|
static nsresult ConvertToProtobuf(
|
||||||
|
|
@ -171,8 +220,10 @@ static nsresult ConvertToProtobuf(
|
||||||
static_cast<content_analysis::sdk::AnalysisConnector>(analysisType);
|
static_cast<content_analysis::sdk::AnalysisConnector>(analysisType);
|
||||||
aOut->set_analysis_connector(connector);
|
aOut->set_analysis_connector(connector);
|
||||||
|
|
||||||
std::string requestToken = GenerateRequestToken();
|
nsCString requestToken;
|
||||||
aOut->set_request_token(requestToken);
|
rv = aIn->GetRequestToken(requestToken);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
aOut->set_request_token(requestToken.get(), requestToken.Length());
|
||||||
|
|
||||||
const std::string tag = "dlp"; // TODO:
|
const std::string tag = "dlp"; // TODO:
|
||||||
*aOut->add_tags() = tag;
|
*aOut->add_tags() = tag;
|
||||||
|
|
@ -338,9 +389,14 @@ ContentAnalysisResponse::ContentAnalysisResponse(
|
||||||
mAction = nsIContentAnalysisResponse::ALLOW;
|
mAction = nsIContentAnalysisResponse::ALLOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRequestToken = aResponse.request_token();
|
const auto& requestToken = aResponse.request_token();
|
||||||
|
mRequestToken.Assign(requestToken.data(), requestToken.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentAnalysisResponse::ContentAnalysisResponse(
|
||||||
|
unsigned long aAction, const nsACString& aRequestToken)
|
||||||
|
: mAction(aAction), mRequestToken(aRequestToken) {}
|
||||||
|
|
||||||
/* static */
|
/* static */
|
||||||
already_AddRefed<ContentAnalysisResponse> ContentAnalysisResponse::FromProtobuf(
|
already_AddRefed<ContentAnalysisResponse> ContentAnalysisResponse::FromProtobuf(
|
||||||
content_analysis::sdk::ContentAnalysisResponse&& aResponse) {
|
content_analysis::sdk::ContentAnalysisResponse&& aResponse) {
|
||||||
|
|
@ -357,6 +413,22 @@ already_AddRefed<ContentAnalysisResponse> ContentAnalysisResponse::FromProtobuf(
|
||||||
return ret.forget();
|
return ret.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
RefPtr<ContentAnalysisResponse> ContentAnalysisResponse::FromAction(
|
||||||
|
unsigned long aAction, const nsACString& aRequestToken) {
|
||||||
|
if (aAction == nsIContentAnalysisResponse::ACTION_UNSPECIFIED) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return RefPtr<ContentAnalysisResponse>(
|
||||||
|
new ContentAnalysisResponse(aAction, aRequestToken));
|
||||||
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
ContentAnalysisResponse::GetRequestToken(nsACString& aRequestToken) {
|
||||||
|
aRequestToken = mRequestToken;
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
static void LogResponse(
|
static void LogResponse(
|
||||||
content_analysis::sdk::ContentAnalysisResponse* aPbResponse) {
|
content_analysis::sdk::ContentAnalysisResponse* aPbResponse) {
|
||||||
if (!static_cast<LogModule*>(gContentAnalysisLog)
|
if (!static_cast<LogModule*>(gContentAnalysisLog)
|
||||||
|
|
@ -398,9 +470,9 @@ static void LogResponse(
|
||||||
}
|
}
|
||||||
|
|
||||||
static nsresult ConvertToProtobuf(
|
static nsresult ConvertToProtobuf(
|
||||||
nsIContentAnalysisAcknowledgement* aIn, const std::string& aRequestToken,
|
nsIContentAnalysisAcknowledgement* aIn, const nsACString& aRequestToken,
|
||||||
content_analysis::sdk::ContentAnalysisAcknowledgement* aOut) {
|
content_analysis::sdk::ContentAnalysisAcknowledgement* aOut) {
|
||||||
aOut->set_request_token(aRequestToken);
|
aOut->set_request_token(aRequestToken.Data(), aRequestToken.Length());
|
||||||
|
|
||||||
uint32_t result;
|
uint32_t result;
|
||||||
nsresult rv = aIn->GetResult(&result);
|
nsresult rv = aIn->GetResult(&result);
|
||||||
|
|
@ -484,8 +556,10 @@ NS_IMETHODIMP ContentAnalysisResult::GetShouldAllowContent(
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMPL_ISUPPORTS(ContentAnalysisRequest, nsIContentAnalysisRequest);
|
NS_IMPL_CLASSINFO(ContentAnalysisRequest, nullptr, 0, {0});
|
||||||
NS_IMPL_ISUPPORTS(ContentAnalysisResponse, nsIContentAnalysisResponse);
|
NS_IMPL_ISUPPORTS_CI(ContentAnalysisRequest, nsIContentAnalysisRequest);
|
||||||
|
NS_IMPL_CLASSINFO(ContentAnalysisResponse, nullptr, 0, {0});
|
||||||
|
NS_IMPL_ISUPPORTS_CI(ContentAnalysisResponse, nsIContentAnalysisResponse);
|
||||||
NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback);
|
NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback);
|
||||||
NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult);
|
NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult);
|
||||||
NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis);
|
NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis);
|
||||||
|
|
@ -545,12 +619,16 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
|
||||||
LOGD("Issuing ContentAnalysisRequest");
|
LOGD("Issuing ContentAnalysisRequest");
|
||||||
LogRequest(&pbRequest);
|
LogRequest(&pbRequest);
|
||||||
|
|
||||||
|
nsCString requestToken;
|
||||||
|
rv = aRequest->GetRequestToken(requestToken);
|
||||||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
||||||
// The content analysis connection is synchronous so run in the background.
|
// The content analysis connection is synchronous so run in the background.
|
||||||
rv = NS_DispatchBackgroundTask(
|
rv = NS_DispatchBackgroundTask(
|
||||||
NS_NewRunnableFunction(
|
NS_NewRunnableFunction(
|
||||||
"RunAnalyzeRequestTask",
|
"RunAnalyzeRequestTask",
|
||||||
[pbRequest = std::move(pbRequest), aCallback = std::move(aCallback),
|
[pbRequest = std::move(pbRequest), aCallback = std::move(aCallback),
|
||||||
owner] {
|
requestToken = std::move(requestToken), owner] {
|
||||||
nsresult rv = NS_ERROR_FAILURE;
|
nsresult rv = NS_ERROR_FAILURE;
|
||||||
content_analysis::sdk::ContentAnalysisResponse pbResponse;
|
content_analysis::sdk::ContentAnalysisResponse pbResponse;
|
||||||
|
|
||||||
|
|
@ -558,14 +636,21 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
|
||||||
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
||||||
"ResolveOnMainThread",
|
"ResolveOnMainThread",
|
||||||
[rv, owner, aCallback = std::move(aCallback),
|
[rv, owner, aCallback = std::move(aCallback),
|
||||||
pbResponse = std::move(pbResponse)]() mutable {
|
pbResponse = std::move(pbResponse), requestToken]() mutable {
|
||||||
if (NS_SUCCEEDED(rv)) {
|
if (NS_SUCCEEDED(rv)) {
|
||||||
LOGD("Content analysis resolving response promise");
|
LOGD(
|
||||||
|
"Content analysis resolving response promise for "
|
||||||
|
"token %s",
|
||||||
|
requestToken.get());
|
||||||
RefPtr<ContentAnalysisResponse> response =
|
RefPtr<ContentAnalysisResponse> response =
|
||||||
ContentAnalysisResponse::FromProtobuf(
|
ContentAnalysisResponse::FromProtobuf(
|
||||||
std::move(pbResponse));
|
std::move(pbResponse));
|
||||||
if (response) {
|
if (response) {
|
||||||
response->SetOwner(owner);
|
response->SetOwner(owner);
|
||||||
|
nsCOMPtr<nsIObserverService> obsServ =
|
||||||
|
mozilla::services::GetObserverService();
|
||||||
|
obsServ->NotifyObservers(response, "dlp-response",
|
||||||
|
nullptr);
|
||||||
aCallback->ContentResult(response);
|
aCallback->ContentResult(response);
|
||||||
} else {
|
} else {
|
||||||
aCallback->Error(NS_ERROR_FAILURE);
|
aCallback->Error(NS_ERROR_FAILURE);
|
||||||
|
|
@ -629,8 +714,11 @@ ContentAnalysis::AnalyzeContentRequestCallback(
|
||||||
return NS_ERROR_NOT_AVAILABLE;
|
return NS_ERROR_NOT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
rv = RunAnalyzeRequestTask(aRequest, aCallback);
|
nsCOMPtr<nsIObserverService> obsServ =
|
||||||
|
mozilla::services::GetObserverService();
|
||||||
|
obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
|
||||||
|
|
||||||
|
rv = RunAnalyzeRequestTask(aRequest, aCallback);
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -643,7 +731,7 @@ ContentAnalysisResponse::Acknowledge(
|
||||||
|
|
||||||
nsresult ContentAnalysis::RunAcknowledgeTask(
|
nsresult ContentAnalysis::RunAcknowledgeTask(
|
||||||
nsIContentAnalysisAcknowledgement* aAcknowledgement,
|
nsIContentAnalysisAcknowledgement* aAcknowledgement,
|
||||||
const std::string& aRequestToken) {
|
const nsACString& aRequestToken) {
|
||||||
bool isActive;
|
bool isActive;
|
||||||
nsresult rv = GetIsActive(&isActive);
|
nsresult rv = GetIsActive(&isActive);
|
||||||
NS_ENSURE_SUCCESS(rv, rv);
|
NS_ENSURE_SUCCESS(rv, rv);
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
#define mozilla_contentanalysis_h
|
#define mozilla_contentanalysis_h
|
||||||
|
|
||||||
#include "mozilla/DataMutex.h"
|
#include "mozilla/DataMutex.h"
|
||||||
|
#include "mozilla/dom/WindowGlobalParent.h"
|
||||||
|
#include "mozilla/Mutex.h"
|
||||||
#include "nsIContentAnalysis.h"
|
#include "nsIContentAnalysis.h"
|
||||||
#include "nsProxyRelease.h"
|
#include "nsProxyRelease.h"
|
||||||
#include "nsString.h"
|
#include "nsString.h"
|
||||||
|
|
@ -22,12 +24,13 @@ namespace mozilla::contentanalysis {
|
||||||
|
|
||||||
class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
|
class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
|
||||||
public:
|
public:
|
||||||
NS_DECL_THREADSAFE_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_NSICONTENTANALYSISREQUEST
|
NS_DECL_NSICONTENTANALYSISREQUEST
|
||||||
|
|
||||||
ContentAnalysisRequest(unsigned long aAnalysisType, nsString&& aString,
|
ContentAnalysisRequest(unsigned long aAnalysisType, nsString&& aString,
|
||||||
bool aStringIsFilePath, nsCString&& aSha256Digest,
|
bool aStringIsFilePath, nsCString&& aSha256Digest,
|
||||||
nsString&& aUrl);
|
nsString&& aUrl, unsigned long aResourceNameType,
|
||||||
|
dom::WindowGlobalParent* aWindowGlobalParent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~ContentAnalysisRequest() = default;
|
~ContentAnalysisRequest() = default;
|
||||||
|
|
@ -56,6 +59,18 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
|
||||||
|
|
||||||
// Email address of user.
|
// Email address of user.
|
||||||
nsString mEmail;
|
nsString mEmail;
|
||||||
|
|
||||||
|
// Unique identifier for this request
|
||||||
|
nsCString mRequestToken;
|
||||||
|
|
||||||
|
// Type of text to display, see nsIContentAnalysisRequest for values
|
||||||
|
unsigned long mOperationTypeForDisplay;
|
||||||
|
|
||||||
|
// String to display if mOperationTypeForDisplay is
|
||||||
|
// OPERATION_CUSTOMDISPLAYSTRING
|
||||||
|
nsString mOperationDisplayString;
|
||||||
|
|
||||||
|
RefPtr<dom::WindowGlobalParent> mWindowGlobalParent;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ContentAnalysisResponse;
|
class ContentAnalysisResponse;
|
||||||
|
|
@ -76,7 +91,7 @@ class ContentAnalysis final : public nsIContentAnalysis {
|
||||||
RefPtr<nsIContentAnalysisCallback> aCallback);
|
RefPtr<nsIContentAnalysisCallback> aCallback);
|
||||||
nsresult RunAcknowledgeTask(
|
nsresult RunAcknowledgeTask(
|
||||||
nsIContentAnalysisAcknowledgement* aAcknowledgement,
|
nsIContentAnalysisAcknowledgement* aAcknowledgement,
|
||||||
const std::string& aRequestToken);
|
const nsACString& aRequestToken);
|
||||||
|
|
||||||
static StaticDataMutex<UniquePtr<content_analysis::sdk::Client>> sCaClient;
|
static StaticDataMutex<UniquePtr<content_analysis::sdk::Client>> sCaClient;
|
||||||
friend class ContentAnalysisResponse;
|
friend class ContentAnalysisResponse;
|
||||||
|
|
@ -84,9 +99,12 @@ class ContentAnalysis final : public nsIContentAnalysis {
|
||||||
|
|
||||||
class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
|
class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
|
||||||
public:
|
public:
|
||||||
NS_DECL_THREADSAFE_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_NSICONTENTANALYSISRESPONSE
|
NS_DECL_NSICONTENTANALYSISRESPONSE
|
||||||
|
|
||||||
|
static RefPtr<ContentAnalysisResponse> FromAction(
|
||||||
|
unsigned long aAction, const nsACString& aRequestToken);
|
||||||
|
|
||||||
void SetOwner(RefPtr<ContentAnalysis> aOwner);
|
void SetOwner(RefPtr<ContentAnalysis> aOwner);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -96,13 +114,16 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
|
||||||
ContentAnalysisResponse& operator=(ContentAnalysisResponse&) = delete;
|
ContentAnalysisResponse& operator=(ContentAnalysisResponse&) = delete;
|
||||||
explicit ContentAnalysisResponse(
|
explicit ContentAnalysisResponse(
|
||||||
content_analysis::sdk::ContentAnalysisResponse&& aResponse);
|
content_analysis::sdk::ContentAnalysisResponse&& aResponse);
|
||||||
|
ContentAnalysisResponse(unsigned long aAction,
|
||||||
|
const nsACString& aRequestToken);
|
||||||
static already_AddRefed<ContentAnalysisResponse> FromProtobuf(
|
static already_AddRefed<ContentAnalysisResponse> FromProtobuf(
|
||||||
content_analysis::sdk::ContentAnalysisResponse&& aResponse);
|
content_analysis::sdk::ContentAnalysisResponse&& aResponse);
|
||||||
|
|
||||||
// See nsIContentAnalysisResponse for values
|
// See nsIContentAnalysisResponse for values
|
||||||
uint32_t mAction;
|
uint32_t mAction;
|
||||||
|
|
||||||
std::string mRequestToken;
|
// Identifier for the corresponding nsIContentAnalysisRequest
|
||||||
|
nsCString mRequestToken;
|
||||||
|
|
||||||
// ContentAnalysis (or, more precisely, it's Client object) must outlive
|
// ContentAnalysis (or, more precisely, it's Client object) must outlive
|
||||||
// the transaction.
|
// the transaction.
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@
|
||||||
|
|
||||||
#include "nsISupports.idl"
|
#include "nsISupports.idl"
|
||||||
|
|
||||||
|
webidl WindowGlobalParent;
|
||||||
|
|
||||||
[scriptable, uuid(06e6a60f-3a2b-41fa-a63b-fea7a7f71649)]
|
[scriptable, uuid(06e6a60f-3a2b-41fa-a63b-fea7a7f71649)]
|
||||||
interface nsIContentAnalysisAcknowledgement : nsISupports
|
interface nsIContentAnalysisAcknowledgement : nsISupports
|
||||||
{
|
{
|
||||||
|
|
@ -48,12 +50,15 @@ interface nsIContentAnalysisResponse : nsISupports
|
||||||
[infallible] readonly attribute unsigned long action;
|
[infallible] readonly attribute unsigned long action;
|
||||||
[infallible] readonly attribute boolean shouldAllowContent;
|
[infallible] readonly attribute boolean shouldAllowContent;
|
||||||
|
|
||||||
|
// Identifier for the corresponding nsIContentAnalysisRequest
|
||||||
|
readonly attribute ACString requestToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acknowledge receipt of an analysis response.
|
* Acknowledge receipt of an analysis response.
|
||||||
* Should always be called after successful resolution of the promise
|
* Should always be called after successful resolution of the promise
|
||||||
* from AnalyzeContentRequest.
|
* from AnalyzeContentRequest.
|
||||||
*/
|
*/
|
||||||
void Acknowledge(in nsIContentAnalysisAcknowledgement aCaa);
|
void acknowledge(in nsIContentAnalysisAcknowledgement aCaa);
|
||||||
};
|
};
|
||||||
|
|
||||||
[scriptable, uuid(48d31df1-204d-42ce-a57f-f156bb870d89)]
|
[scriptable, uuid(48d31df1-204d-42ce-a57f-f156bb870d89)]
|
||||||
|
|
@ -104,6 +109,13 @@ interface nsIContentAnalysisRequest : nsISupports
|
||||||
|
|
||||||
readonly attribute unsigned long analysisType;
|
readonly attribute unsigned long analysisType;
|
||||||
|
|
||||||
|
// Enumeration of what operation is happening, to be displayed to the user
|
||||||
|
const unsigned long OPERATION_CUSTOMDISPLAYSTRING = 0;
|
||||||
|
const unsigned long OPERATION_CLIPBOARD = 1;
|
||||||
|
const unsigned long OPERATION_DROPPEDTEXT = 2;
|
||||||
|
readonly attribute unsigned long operationTypeForDisplay;
|
||||||
|
readonly attribute AString operationDisplayString;
|
||||||
|
|
||||||
// Text content to analyze. Only one of textContent or filePath is defined.
|
// Text content to analyze. Only one of textContent or filePath is defined.
|
||||||
readonly attribute AString textContent;
|
readonly attribute AString textContent;
|
||||||
|
|
||||||
|
|
@ -122,6 +134,12 @@ interface nsIContentAnalysisRequest : nsISupports
|
||||||
|
|
||||||
// Email address of user.
|
// Email address of user.
|
||||||
readonly attribute AString email;
|
readonly attribute AString email;
|
||||||
|
|
||||||
|
// Unique identifier for this request
|
||||||
|
readonly attribute ACString requestToken;
|
||||||
|
|
||||||
|
// The window associated with this request
|
||||||
|
readonly attribute WindowGlobalParent windowGlobalParent;
|
||||||
};
|
};
|
||||||
|
|
||||||
[scriptable, builtinclass, uuid(9679545f-4256-4c90-9654-90292c355d25)]
|
[scriptable, builtinclass, uuid(9679545f-4256-4c90-9654-90292c355d25)]
|
||||||
|
|
@ -169,11 +187,11 @@ interface nsIContentAnalysis : nsISupports
|
||||||
* See @nsIContentAnalysisResponse.
|
* See @nsIContentAnalysisResponse.
|
||||||
*/
|
*/
|
||||||
[implicit_jscontext]
|
[implicit_jscontext]
|
||||||
Promise AnalyzeContentRequest(in nsIContentAnalysisRequest aCar);
|
Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same functionality as AnalyzeContentRequest(), but more convenient to call
|
* Same functionality as AnalyzeContentRequest(), but more convenient to call
|
||||||
* from C++ since it takes a callback instead of returning a Promise.
|
* from C++ since it takes a callback instead of returning a Promise.
|
||||||
*/
|
*/
|
||||||
void AnalyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in nsIContentAnalysisCallback callback);
|
void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in nsIContentAnalysisCallback callback);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
contentanalysis-alert-title = Content Analysis
|
||||||
|
|
||||||
|
# Variables:
|
||||||
|
# $content - Description of the content being warned about, such as "clipboard" or "aFile.txt"
|
||||||
|
contentanalysis-slow-agent-notification = The Content Analysis tool is taking a long time to respond for resource “{ $content }”
|
||||||
|
contentanalysis-slow-agent-dialog-title = Content analysis in progress
|
||||||
|
|
||||||
|
# Variables:
|
||||||
|
# $content - Description of the content being warned about, such as "clipboard" or "aFile.txt"
|
||||||
|
contentanalysis-slow-agent-dialog-body = Content Analysis is analyzing resource “{ $content }”
|
||||||
|
contentanalysis-operationtype-clipboard = clipboard
|
||||||
|
contentanalysis-operationtype-dropped-text = dropped text
|
||||||
|
|
||||||
|
contentanalysis-notification-title = Content Analysis
|
||||||
|
# Variables:
|
||||||
|
# $content - Description of the content being reported, such as "clipboard" or "aFile.txt"
|
||||||
|
# $response - The response received from the content analysis agent, such as "REPORT_ONLY"
|
||||||
|
contentanalysis-genericresponse-message = Content Analysis responded with { $response } for resource: { $content }
|
||||||
|
# Variables:
|
||||||
|
# $content - Description of the content being blocked, such as "clipboard" or "aFile.txt"
|
||||||
|
contentanalysis-block-message = Your organization uses data-loss prevention software that has blocked this content: { $content }.
|
||||||
|
# Variables:
|
||||||
|
# $content - Description of the content being blocked, such as "clipboard" or "aFile.txt"
|
||||||
|
contentanalysis-error-message = An error occurred in communicating with the data-loss prevention software. Transfer denied for resource: { $content }.
|
||||||
Loading…
Reference in a new issue