Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 helper methods for JS code that need to call into
* Content Analysis. Note that most JS code will not need to explicitly
* use this - this is only for edge cases that the existing C++ code
* does not handle, such as pasting into a prompt() or pasting into
* the GenAI chatbot shortcut menu.
*/
// @ts-check
export const ContentAnalysisUtils = {
/**
* Sets up Content Analysis to monitor clipboard pastes and drag-and-drop
* and send the text on to Content Analysis for approval. This method
* will check if Content Analysis is active and if not it will return early.
*
* @param {HTMLInputElement} textElement The DOM element to monitor
* @param {CanonicalBrowsingContext} browsingContext The browsing context that the textElement
* is part of. Used to show the "DLP busy" dialog.
* @param {nsIURI} url An nsIURI that indicates where the content would be sent to.
* If this is undefined, this method will get the URI from the browsingContext.
*/
setupContentAnalysisEventsForTextElement(textElement, browsingContext, url) {
// Do not use a lazy service getter for this, because tests set up different mocks,
// so if multiple tests run that call into this we can end up calling into an old mock.
const contentAnalysis = Cc["@mozilla.org/contentanalysis;1"].getService(
Ci.nsIContentAnalysis
);
if (!textElement || !contentAnalysis.isActive) {
return;
}
let caEventChecker = async event => {
let isPaste = event.type == "paste";
let dataTransfer = isPaste ? event.clipboardData : event.dataTransfer;
let data = dataTransfer.getData("text/plain");
if (!data || !data.length) {
return;
}
// Prevent the paste/drop from happening until content analysis returns a response
event.preventDefault();
// Selections can be forward or backward, so use min/max
const startIndex = Math.min(
textElement.selectionStart,
textElement.selectionEnd
);
const endIndex = Math.max(
textElement.selectionStart,
textElement.selectionEnd
);
const selectionDirection = endIndex < startIndex ? "backward" : "forward";
try {
const response = await contentAnalysis.analyzeContentRequests(
[
// Specify an explicit type here to suppress type errors about the missing
// properties, because there are a ton of them that make this hard to read.
/** @type {nsIContentAnalysisRequest} */
({
analysisType: Ci.nsIContentAnalysisRequest.eBulkDataEntry,
reason: isPaste
? Ci.nsIContentAnalysisRequest.eClipboardPaste
: Ci.nsIContentAnalysisRequest.eDragAndDrop,
resources: [],
operationTypeForDisplay: isPaste
? Ci.nsIContentAnalysisRequest.eClipboard
: Ci.nsIContentAnalysisRequest.eDroppedText,
url:
url ??
contentAnalysis.getURIForBrowsingContext(browsingContext),
textContent: data,
/* browsingContext can sometimes be undefined in tests where content
is being pasted into chrome (specifically the GenAI custom chat shortcut) */
windowGlobalParent: browsingContext?.currentWindowContext,
}),
],
true
);
if (response.shouldAllowContent) {
textElement.value =
textElement.value.slice(0, startIndex) +
data +
textElement.value.slice(endIndex);
textElement.focus();
if (startIndex !== endIndex) {
// Select the pasted text
textElement.setSelectionRange(
startIndex,
startIndex + data.length,
selectionDirection
);
}
}
} catch (error) {
console.error("Content analysis request returned error: ", error);
}
};
textElement.addEventListener("paste", caEventChecker);
textElement.addEventListener("drop", caEventChecker);
},
};