From 1a51dc78635513e4173cf05cac502d6394281d4e Mon Sep 17 00:00:00 2001 From: David P Date: Thu, 13 Feb 2025 19:19:18 +0000 Subject: [PATCH] Bug 1936020: Part 8 - Update the content analysis service's callers r=dlp-reviewers,win-reviewers,reusable-components-reviewers,gstoll,tgiles AnalyzeContentRequest* calls now require fewer fields. For example, the URI is, by default, now obtained from the request's WindowGlobalParent's BrowsingContext. browser-custom-element.js needed to re-get service for tests, since it was sometimes getting an old mock version from a previous test. ContentAnalysisCallback does what SafeContentAnalysisResultCallback used to do. Differential Revision: https://phabricator.services.mozilla.com/D236631 --- .../prompts/content/commonDialog.js | 30 ++-- .../content/widgets/browser-custom-element.js | 133 ++++-------------- widget/ClipboardContentAnalysisParent.cpp | 8 +- widget/nsBaseClipboard.cpp | 6 +- widget/windows/nsFilePicker.cpp | 94 ++++--------- 5 files changed, 77 insertions(+), 194 deletions(-) diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js index 32ea1747ab5e..5dbdb95b4c6a 100644 --- a/toolkit/components/prompts/content/commonDialog.js +++ b/toolkit/components/prompts/content/commonDialog.js @@ -179,20 +179,22 @@ function commonDialogOnLoad() { const selectionDirection = endIndex < startIndex ? "backward" : "forward"; try { - const response = await lazy.gContentAnalysis.analyzeContentRequest( - { - requestToken: Services.uuid.generateUUID().toString(), - resources: [], - analysisType: Ci.nsIContentAnalysisRequest.eBulkDataEntry, - reason: Ci.nsIContentAnalysisRequest.eClipboardPaste, - operationTypeForDisplay: Ci.nsIContentAnalysisRequest.eClipboard, - url: lazy.gContentAnalysis.getURIForBrowsingContext( - args.owningBrowsingContext - ), - textContent: data, - windowGlobalParent: - args.owningBrowsingContext.currentWindowContext, - }, + const response = await lazy.gContentAnalysis.analyzeContentRequests( + [ + { + analysisType: Ci.nsIContentAnalysisRequest.eBulkDataEntry, + reason: Ci.nsIContentAnalysisRequest.eClipboardPaste, + resources: [], + operationTypeForDisplay: + Ci.nsIContentAnalysisRequest.eClipboard, + url: lazy.gContentAnalysis.getURIForBrowsingContext( + args.owningBrowsingContext + ), + textContent: data, + windowGlobalParent: + args.owningBrowsingContext.currentWindowContext, + }, + ], true ); if (response.shouldAllowContent) { diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js index 345b257cc068..0d0cc943c69e 100644 --- a/toolkit/content/widgets/browser-custom-element.js +++ b/toolkit/content/widgets/browser-custom-element.js @@ -30,25 +30,12 @@ Services.io.newURI("about:blank") ); - XPCOMUtils.defineLazyServiceGetter( - lazy, - "contentAnalysis", - "@mozilla.org/contentanalysis;1", - Ci.nsIContentAnalysis - ); - let lazyPrefs = {}; XPCOMUtils.defineLazyPreferenceGetter( lazyPrefs, "unloadTimeoutMs", "dom.beforeunload_timeout_ms" ); - XPCOMUtils.defineLazyPreferenceGetter( - lazyPrefs, - "_contentAnalysisDragDropEnabled", - "browser.contentanalysis.interception_point.drag_and_drop.enabled", - true - ); Object.defineProperty(lazy, "ProcessHangMonitor", { configurable: true, @@ -173,10 +160,10 @@ this.addEventListener( "drop", event => { - if ( - lazy.contentAnalysis.isActive && - lazyPrefs._contentAnalysisDragDropEnabled - ) { + const contentAnalysis = Cc[ + "@mozilla.org/contentanalysis;1" + ].getService(Ci.nsIContentAnalysis); + if (contentAnalysis.isActive) { let dragService = Cc[ "@mozilla.org/widget/dragservice;1" ].getService(Ci.nsIDragService); @@ -185,102 +172,30 @@ return; } - // Don't check items from drags that take place inside of a single - // frame, or in a same-origin iframe hierarchy. Drag sessions with an - // external source have no sourceWindowContext and must be checked. - let sourceWC = dragSession.sourceWindowContext; - let targetWC = this.browsingContext?.currentWindowContext; - if (!targetWC) { - return; - } - if ( - sourceWC && - sourceWC.browsingContext.top == targetWC.browsingContext.top && - targetWC.documentPrincipal?.subsumes(sourceWC.documentPrincipal) - ) { - return; - } - - // Create a request for each text and file item in the DataTransfer try { + // Submit a content analysis request for the DataTransfer and + // stop dispatching this drop event. Reissue the drop if all + // requests are permitted, otherwise issue a dragexit. + let request = { + analysisType: Ci.nsIContentAnalysisRequest.eBulkDataEntry, + dataTransfer: event.dataTransfer, + operationTypeForDisplay: + Ci.nsIContentAnalysisRequest.eDroppedText, + reason: Ci.nsIContentAnalysisRequest.eDragAndDrop, + resources: [], + sourceWindowGlobal: dragSession.sourceWindowContext, + uri: contentAnalysis.getURIForDropEvent(event), + windowGlobalParent: this.browsingContext.currentWindowContext, + }; + // Tell browser to record the event target and to delay EndDragSession // until the content analysis results are given. dragSession.sendStoreDropTargetAndDelayEndDragSession(event); - // Submit a content analysis request for each checkable entry in the - // DataTransfer and stop dispatching this drop event. Reissue the - // drop if all requests are permitted. - let caPromises = []; - let items = event.dataTransfer.items; - for (let elt of items) { - const kTextMimeTypes = [ - "text/plain", - "text/html", - "application/x-moz-nativehtml", - ]; - - let requestFields; - if ( - elt.kind === "string" && - kTextMimeTypes.includes(elt.type) - ) { - let str = event.dataTransfer.getData(elt.type); - if (!str) { - continue; - } - requestFields = { - analysisType: Ci.nsIContentAnalysisRequest.eBulkDataEntry, - operationTypeForDisplay: - Ci.nsIContentAnalysisRequest.eDroppedText, - textContent: str, - }; - } else if (elt.kind === "file") { - let file = elt.getAsFile(); - requestFields = { - analysisType: Ci.nsIContentAnalysisRequest.eFileAttached, - operationTypeForDisplay: - Ci.nsIContentAnalysisRequest.eCustomDisplayString, - operationDisplayString: file.name, - filePath: file.mozFullPath, - }; - } else { - // Unrecognized data type -- don't send to content analysis - continue; - } - - caPromises.push( - lazy.contentAnalysis.analyzeContentRequest( - { - reason: Ci.nsIContentAnalysisRequest.eDragAndDrop, - requestToken: Services.uuid.generateUUID().toString(), - resources: [], - url: lazy.contentAnalysis.getURIForDropEvent(event), - windowGlobalParent: - this.browsingContext.currentWindowContext, - ...requestFields, - }, - true /* autoAcknowledge */ - ) - ); - } - - if (!caPromises.length) { - // Nothing was analyzable. - dragSession.sendDispatchToDropTargetAndResumeEndDragSession( - true - ); - return; - } - - // Only permit the drop if all requests were approved. Issue dragexit - // instead of drop if CA rejected the content or there was an error. - Promise.all(caPromises).then( - caResults => { - let allApproved = caResults.reduce((prev, current) => { - return prev && current.shouldAllowContent; - }, true); + contentAnalysis.analyzeContentRequest(request, true).then( + caResult => { dragSession.sendDispatchToDropTargetAndResumeEndDragSession( - allApproved + caResult.shouldAllowContent ); }, () => { @@ -293,7 +208,9 @@ // Do not allow this drop to continue dispatch. event.preventDefault(); event.stopPropagation(); - } catch { + } catch (e) { + console.error(`content analysis dnd error: ${e}`); + // On internal error, deny any drop. CA has its own behavior to // handle internal errors, like a lost connection to the agent, but // we are more strict when facing errors here. diff --git a/widget/ClipboardContentAnalysisParent.cpp b/widget/ClipboardContentAnalysisParent.cpp index dcb9b09aea2f..7f8d03d9ce35 100644 --- a/widget/ClipboardContentAnalysisParent.cpp +++ b/widget/ClipboardContentAnalysisParent.cpp @@ -119,16 +119,14 @@ static RefPtr GetClipboardImpl( auto resultPromise = MakeRefPtr(__func__); auto contentAnalysisCallback = - mozilla::MakeRefPtr( + mozilla::MakeRefPtr( [transferable, resultPromise, cpHandle = RefPtr{aRequestingContentParent}]( - RefPtr&& aResult) { + nsIContentAnalysisResult* aResult) { // Needed to call cpHandle->GetContentParent() AssertIsOnMainThread(); - bool shouldAllow = aResult->GetShouldAllowContent(); - if (!shouldAllow) { + if (!aResult->GetShouldAllowContent()) { resultPromise->Reject(NS_ERROR_CONTENT_BLOCKED, __func__); return; } diff --git a/widget/nsBaseClipboard.cpp b/widget/nsBaseClipboard.cpp index 0c5485558f1a..81e1ce00ba5f 100644 --- a/widget/nsBaseClipboard.cpp +++ b/widget/nsBaseClipboard.cpp @@ -1017,11 +1017,9 @@ NS_IMETHODIMP nsBaseClipboard::ClipboardDataSnapshot::GetData( MOZ_ASSERT(mClipboard); auto contentAnalysisCallback = - mozilla::MakeRefPtr( + mozilla::MakeRefPtr( [transferable = nsCOMPtr{aTransferable}, - callback = nsCOMPtr{aCallback}]( - RefPtr&& aResult) { + callback = nsCOMPtr{aCallback}](nsIContentAnalysisResult* aResult) { if (aResult->GetShouldAllowContent()) { callback->OnComplete(NS_OK); } else { diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp index 410ec6c862a3..08d86081c76d 100644 --- a/widget/windows/nsFilePicker.cpp +++ b/widget/windows/nsFilePicker.cpp @@ -455,26 +455,8 @@ static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args) -> }); } -// Asynchronously invokes `aPredicate` on each member of `aItems`. -// Yields `false` (and stops immediately) if any invocation of -// `predicate` yielded `false`; otherwise yields `true`. -template -static RefPtr> AsyncAll( - nsTArray aItems, - std::function< - RefPtr>(const T& item)> - aPredicate) { - auto promise = - mozilla::MakeRefPtr::Private>( - __func__); - auto iterator = mozilla::MakeRefPtr>( - std::move(aItems), aPredicate, promise); - iterator->StartIterating(); - return promise; -} } // namespace fd_async -using fd_async::AsyncAll; using fd_async::AsyncExecute; } // namespace mozilla::detail @@ -713,14 +695,6 @@ nsFilePicker::CheckContentAnalysisService() { __func__); } - nsCOMPtr uri = - mozilla::contentanalysis::ContentAnalysis::GetURIForBrowsingContext( - mBrowsingContext->Canonical()); - if (!uri) { - return nsFilePicker::ContentAnalysisResponse::CreateAndReject( - NS_ERROR_FAILURE, __func__); - } - // Entries may be files or folders. Folder contents will be recursively // checked. nsTArray filePaths; @@ -737,47 +711,41 @@ nsFilePicker::CheckContentAnalysisService() { std::transform(mFiles.begin(), mFiles.end(), MakeBackInserter(filePaths), [](auto* entry) { return entry->NativePath(); }); } + MOZ_ASSERT(!filePaths.IsEmpty()); - auto processOneItem = [self = RefPtr{this}, - contentAnalysis = std::move(contentAnalysis), - uri = - std::move(uri)](const mozilla::PathString& aItem) { - nsCString emptyDigestString; - auto* windowGlobal = - self->mBrowsingContext->Canonical()->GetCurrentWindowGlobal(); - nsCOMPtr contentAnalysisRequest( - new mozilla::contentanalysis::ContentAnalysisRequest( - nsIContentAnalysisRequest::AnalysisType::eFileAttached, - nsIContentAnalysisRequest::Reason::eFilePickerDialog, aItem, true, - std::move(emptyDigestString), uri, - nsIContentAnalysisRequest::OperationType::eCustomDisplayString, - windowGlobal)); + auto promise = + mozilla::MakeRefPtr( + __func__); + auto contentAnalysisCallback = + mozilla::MakeRefPtr( + [promise](nsIContentAnalysisResult* aResult) { + promise->Resolve(aResult->GetShouldAllowContent(), __func__); + }); - auto promise = - mozilla::MakeRefPtr( - __func__); - auto contentAnalysisCallback = - mozilla::MakeRefPtr( - [promise](nsIContentAnalysisResponse* aResponse) { - bool shouldAllow = false; - mozilla::DebugOnly rv = - aResponse->GetShouldAllowContent(&shouldAllow); - MOZ_ASSERT(NS_SUCCEEDED(rv)); - promise->Resolve(shouldAllow, __func__); - }, - [promise](nsresult aError) { promise->Reject(aError, __func__); }); + auto* windowGlobal = mBrowsingContext->Canonical()->GetCurrentWindowGlobal(); + NS_ENSURE_TRUE( + windowGlobal, + nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv, __func__)); - nsresult rv = contentAnalysis->AnalyzeContentRequestCallback( - contentAnalysisRequest, /* aAutoAcknowledge */ true, - contentAnalysisCallback); - if (NS_WARN_IF(NS_FAILED(rv))) { - promise->Reject(rv, __func__); - } - return promise; - }; + nsTArray> requests(filePaths.Length()); + for (auto& path : filePaths) { +#ifdef XP_WIN + nsString pathString(std::move(path)); +#else + nsString pathString = NS_ConvertUTF8toUTF16(path); +#endif - return mozilla::detail::AsyncAll(std::move(filePaths), - processOneItem); + requests.AppendElement(new mozilla::contentanalysis::ContentAnalysisRequest( + nsIContentAnalysisRequest::AnalysisType::eFileAttached, + nsIContentAnalysisRequest::Reason::eFilePickerDialog, pathString, + true /* aStringIsFilePath */, EmptyCString(), nullptr, + nsIContentAnalysisRequest::OperationType::eCustomDisplayString, + windowGlobal)); + } + + contentAnalysis->AnalyzeContentRequestsCallback( + requests, true /* aAutoAcknowledge */, contentAnalysisCallback); + return promise; }; ///////////////////////////////////////////////////////////////////////////////