diff --git a/browser/components/contentanalysis/content/ContentAnalysis.sys.mjs b/browser/components/contentanalysis/content/ContentAnalysis.sys.mjs index 85cfb7b86156..b56766ac77fa 100644 --- a/browser/components/contentanalysis/content/ContentAnalysis.sys.mjs +++ b/browser/components/contentanalysis/content/ContentAnalysis.sys.mjs @@ -16,6 +16,13 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; const lazy = {}; +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gContentAnalysis", + "@mozilla.org/contentanalysis;1", + Ci.nsIContentAnalysis +); + ChromeUtils.defineESModuleGetters(lazy, { clearTimeout: "resource://gre/modules/Timer.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs", @@ -387,7 +394,10 @@ export const ContentAnalysis = { }, _shouldShowBlockingNotification(aOperation) { - return false; + return !( + aOperation == Ci.nsIContentAnalysisRequest.FILE_DOWNLOADED || + aOperation == Ci.nsIContentAnalysisRequest.PRINT + ); }, // This function also transforms the nameOrL10NId so we won't have to @@ -486,7 +496,8 @@ export const ContentAnalysis = { content: this._getResourceNameFromNameOrL10NId(aResourceNameOrL10NId), }), Ci.nsIPromptService.BUTTON_POS_0 * - Ci.nsIPromptService.BUTTON_TITLE_CANCEL, + Ci.nsIPromptService.BUTTON_TITLE_CANCEL + + Ci.nsIPromptService.SHOW_SPINNER, null, null, null, @@ -504,6 +515,7 @@ export const ContentAnalysis = { // 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)) { + lazy.gContentAnalysis.cancelContentAnalysisRequest(aRequestToken); let dlpBusyView = this.dlpBusyViewsByTopBrowsingContext.getEntry(aBrowsingContext); if (dlpBusyView) { diff --git a/netwerk/base/nsIPrompt.idl b/netwerk/base/nsIPrompt.idl index dde9d70a7446..75afd6108e02 100644 --- a/netwerk/base/nsIPrompt.idl +++ b/netwerk/base/nsIPrompt.idl @@ -56,6 +56,8 @@ interface nsIPrompt : nsISupports const unsigned long BUTTON_DELAY_ENABLE = 1 << 26; + const unsigned long SHOW_SPINNER = 1 << 27; + const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) + (BUTTON_TITLE_CANCEL * BUTTON_POS_1); const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) + diff --git a/toolkit/components/contentanalysis/ContentAnalysis.cpp b/toolkit/components/contentanalysis/ContentAnalysis.cpp index 9f931ffb6e42..0fc5e9da6e60 100644 --- a/toolkit/components/contentanalysis/ContentAnalysis.cpp +++ b/toolkit/components/contentanalysis/ContentAnalysis.cpp @@ -9,6 +9,7 @@ #include "content_analysis/sdk/analysis_client.h" #include "base/process_util.h" +#include "mozilla/Components.h" #include "mozilla/dom/Promise.h" #include "mozilla/Logging.h" #include "mozilla/ScopeExit.h" @@ -32,6 +33,19 @@ # include #endif +namespace mozilla::contentanalysis { + +LazyLogModule gContentAnalysisLog("contentanalysis"); +#define LOGD(...) \ + MOZ_LOG(mozilla::contentanalysis::gContentAnalysisLog, \ + mozilla::LogLevel::Debug, (__VA_ARGS__)) + +#define LOGE(...) \ + MOZ_LOG(mozilla::contentanalysis::gContentAnalysisLog, \ + mozilla::LogLevel::Error, (__VA_ARGS__)) + +} + namespace { const char* kIsDLPEnabledPref = "browser.contentanalysis.enabled"; @@ -72,9 +86,6 @@ static nsresult GetFileDisplayName(const nsString& aFilePath, namespace mozilla::contentanalysis { -LazyLogModule gContentAnalysisLog("contentanalysis"); -#define LOGD(...) MOZ_LOG(gContentAnalysisLog, LogLevel::Debug, (__VA_ARGS__)) - NS_IMETHODIMP ContentAnalysisRequest::GetAnalysisType(uint32_t* aAnalysisType) { *aAnalysisType = mAnalysisType; @@ -144,25 +155,19 @@ ContentAnalysisRequest::GetWindowGlobalParent( return NS_OK; } +nsresult ContentAnalysis::CreateContentAnalysisClient(nsCString&& aPipePathName, + bool aIsPerUser) { + MOZ_ASSERT(!NS_IsMainThread()); + + MOZ_ASSERT(!mCaClientPromise->IsResolved()); -StaticDataMutex> - ContentAnalysis::sCaClient("ContentAnalysisClient"); - -nsresult ContentAnalysis::EnsureContentAnalysisClient() { - auto caClientRef = sCaClient.Lock(); - auto& caClient = caClientRef.ref(); - if (caClient) { - return NS_OK; - } - - nsAutoCString pipePathName; - Preferences::GetCString(kPipePathNamePref, pipePathName); - caClient.reset( - content_analysis::sdk::Client::Create( - {pipePathName.Data(), Preferences::GetBool(kIsPerUserPref)}) + std::shared_ptr client( + content_analysis::sdk::Client::Create({aPipePathName.Data(), aIsPerUser}) .release()); - LOGD("Content analysis is %s", caClient ? "connected" : "not available"); - return caClient ? NS_OK : NS_ERROR_NOT_AVAILABLE; + LOGD("Content analysis is %s", client ? "connected" : "not available"); + mCaClientPromise->Resolve(client, __func__); + + return NS_OK; } ContentAnalysisRequest::ContentAnalysisRequest( @@ -562,26 +567,61 @@ NS_IMPL_CLASSINFO(ContentAnalysisResponse, nullptr, 0, {0}); NS_IMPL_ISUPPORTS_CI(ContentAnalysisResponse, nsIContentAnalysisResponse); NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback); NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult); -NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis); +NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis, ContentAnalysis); + +ContentAnalysis::ContentAnalysis() + : mCaClientPromise( + new ClientPromise::Private("ContentAnalysis::ContentAnalysis")), + mClientCreationAttempted(false), + mCallbackMap("ContentAnalysis::mCallbackMap") {} ContentAnalysis::~ContentAnalysis() { - auto caClientRef = sCaClient.Lock(); - auto& caClient = caClientRef.ref(); - caClient = nullptr; + + MOZ_ASSERT(NS_IsMainThread()); + if (!mClientCreationAttempted) { + + mCaClientPromise->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } } NS_IMETHODIMP ContentAnalysis::GetIsActive(bool* aIsActive) { *aIsActive = false; + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(XRE_IsParentProcess()); if (!gAllowContentAnalysis || !Preferences::GetBool(kIsDLPEnabledPref)) { + LOGD("Local DLP Content Analysis is not active"); return NS_OK; } - - nsresult rv = EnsureContentAnalysisClient(); - *aIsActive = NS_SUCCEEDED(rv); - LOGD("Local DLP Content Analysis is %sactive", *aIsActive ? "" : "not "); + *aIsActive = true; + LOGD("Local DLP Content Analysis is active"); + + + if (!mClientCreationAttempted) { + mClientCreationAttempted = true; + LOGD("Dispatching background task to create Content Analysis client"); + + nsCString pipePathName; + nsresult rv = Preferences::GetCString(kPipePathNamePref, pipePathName); + if (NS_WARN_IF(NS_FAILED(rv))) { + mCaClientPromise->Reject(rv, __func__); + return rv; + } + bool isPerUser = Preferences::GetBool(kIsPerUserPref); + rv = NS_DispatchBackgroundTask(NS_NewCancelableRunnableFunction( + "ContentAnalysis::CreateContentAnalysisClient", + [owner = RefPtr{this}, pipePathName = std::move(pipePathName), + isPerUser]() mutable { + owner->CreateContentAnalysisClient(std::move(pipePathName), + isPerUser); + })); + if (NS_WARN_IF(NS_FAILED(rv))) { + mCaClientPromise->Reject(rv, __func__); + return rv; + } + } return NS_OK; } @@ -596,6 +636,41 @@ ContentAnalysis::GetMightBeActive(bool* aMightBeActive) { return NS_OK; } +nsresult ContentAnalysis::CancelWithError(nsCString aRequestToken, + nsresult aResult) { + return NS_DispatchToMainThread(NS_NewCancelableRunnableFunction( + "ContentAnalysis::RunAnalyzeRequestTask::HandleResponse", + [aResult, aRequestToken = std::move(aRequestToken)] { + RefPtr owner = GetContentAnalysisFromService(); + if (!owner) { + + return; + } + nsMainThreadPtrHandle callbackHolder; + { + auto lock = owner->mCallbackMap.Lock(); + auto callbackData = lock->Extract(aRequestToken); + if (callbackData.isSome()) { + callbackHolder = callbackData->TakeCallbackHolder(); + } + } + if (callbackHolder) { + callbackHolder->Error(aResult); + } + })); +} + +RefPtr ContentAnalysis::GetContentAnalysisFromService() { + RefPtr contentAnalysisService = + mozilla::components::nsIContentAnalysis::Service(); + if (!contentAnalysisService) { + + return nullptr; + } + + return contentAnalysisService; +} + nsresult ContentAnalysis::RunAnalyzeRequestTask( RefPtr aRequest, RefPtr aCallback) { @@ -603,15 +678,11 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask( auto callbackCopy = aCallback; auto se = MakeScopeExit([&] { if (!NS_SUCCEEDED(rv)) { - LOGD("RunAnalyzeRequestTask failed"); + LOGE("RunAnalyzeRequestTask failed"); callbackCopy->Error(rv); } }); - - - RefPtr owner = this; - content_analysis::sdk::ContentAnalysisRequest pbRequest; rv = ConvertToProtobuf(aRequest, &pbRequest); NS_ENSURE_SUCCESS(rv, rv); @@ -620,69 +691,142 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask( LogRequest(&pbRequest); nsCString requestToken; + nsMainThreadPtrHandle callbackHolderCopy( + new nsMainThreadPtrHolder( + "content analysis callback", aCallback)); + CallbackData callbackData(std::move(callbackHolderCopy)); rv = aRequest->GetRequestToken(requestToken); NS_ENSURE_SUCCESS(rv, rv); + { + auto lock = mCallbackMap.Lock(); + lock->InsertOrUpdate(requestToken, std::move(callbackData)); + } - - rv = NS_DispatchBackgroundTask( - NS_NewRunnableFunction( - "RunAnalyzeRequestTask", - [pbRequest = std::move(pbRequest), aCallback = std::move(aCallback), - requestToken = std::move(requestToken), owner] { - nsresult rv = NS_ERROR_FAILURE; - content_analysis::sdk::ContentAnalysisResponse pbResponse; - - auto resolveOnMainThread = MakeScopeExit([&] { - NS_DispatchToMainThread(NS_NewRunnableFunction( - "ResolveOnMainThread", - [rv, owner, aCallback = std::move(aCallback), - pbResponse = std::move(pbResponse), requestToken]() mutable { - if (NS_SUCCEEDED(rv)) { + LOGD("Issuing ContentAnalysisRequest for token %s", requestToken.get()); + LogRequest(&pbRequest); + + mCaClientPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [requestToken, pbRequest = std::move(pbRequest)]( + std::shared_ptr client) { + + NS_DispatchBackgroundTask( + NS_NewCancelableRunnableFunction( + __func__, + [requestToken, pbRequest = std::move(pbRequest), client] { + RefPtr owner = + GetContentAnalysisFromService(); + if (!owner) { + + return; + } + + if (!client) { + owner->CancelWithError(std::move(requestToken), + NS_ERROR_NOT_AVAILABLE); + return; + } + { + auto callbackMap = owner->mCallbackMap.Lock(); + if (!callbackMap->Contains(requestToken)) { LOGD( - "Content analysis resolving response promise for " - "token %s", + "RunAnalyzeRequestTask token %s has already been " + "cancelled - not issuing request", requestToken.get()); - RefPtr response = - ContentAnalysisResponse::FromProtobuf( - std::move(pbResponse)); - if (response) { + return; + } + } + + + + content_analysis::sdk::ContentAnalysisResponse pbResponse; + int err = client->Send(pbRequest, &pbResponse); + if (err != 0) { + LOGE("RunAnalyzeRequestTask client transaction failed"); + owner->CancelWithError(std::move(requestToken), + NS_ERROR_FAILURE); + return; + } + LOGD("Content analysis client transaction succeeded"); + LogResponse(&pbResponse); + NS_DispatchToMainThread(NS_NewCancelableRunnableFunction( + "ContentAnalysis::RunAnalyzeRequestTask::HandleResponse", + [pbResponse = std::move(pbResponse)]() mutable { + RefPtr owner = + GetContentAnalysisFromService(); + if (!owner) { + + return; + } + + RefPtr response = + ContentAnalysisResponse::FromProtobuf( + std::move(pbResponse)); + if (!response) { + LOGE("Content analysis got invalid response!"); + return; + } + nsCString responseRequestToken; + nsresult requestRv = + response->GetRequestToken(responseRequestToken); + if (NS_FAILED(requestRv)) { + LOGE( + "Content analysis couldn't get request token " + "from response!"); + return; + } + + Maybe maybeCallbackData; + { + auto callbackMap = owner->mCallbackMap.Lock(); + maybeCallbackData = + callbackMap->Extract(responseRequestToken); + } + if (maybeCallbackData.isNothing()) { + LOGD( + "Content analysis did not find callback for " + "token %s", + responseRequestToken.get()); + return; + } response->SetOwner(owner); + if (maybeCallbackData->Canceled()) { + + + LOGD( + "Content analysis got response but ignoring " + "because it was already cancelled for token %s", + responseRequestToken.get()); + return; + } + + LOGD( + "Content analysis resolving response promise for " + "token %s", + responseRequestToken.get()); nsCOMPtr obsServ = mozilla::services::GetObserverService(); + obsServ->NotifyObservers(response, "dlp-response", nullptr); - aCallback->ContentResult(response); - } else { - aCallback->Error(NS_ERROR_FAILURE); - } - } else { - aCallback->Error(rv); - } - })); - }); - - auto caClientRef = sCaClient.Lock(); - auto& caClient = caClientRef.ref(); - if (!caClient) { - LOGD("RunAnalyzeRequestTask failed to get client"); - rv = NS_ERROR_NOT_AVAILABLE; - return; - } - - - - int err = caClient->Send(pbRequest, &pbResponse); - if (err != 0) { - LOGD("RunAnalyzeRequestTask client transaction failed"); - rv = NS_ERROR_FAILURE; - return; - } - - LOGD("Content analysis client transaction succeeded"); - LogResponse(&pbResponse); - rv = NS_OK; - }), - NS_DISPATCH_EVENT_MAY_BLOCK); + + nsMainThreadPtrHandle + callbackHolder = + maybeCallbackData->TakeCallbackHolder(); + callbackHolder->ContentResult(response); + })); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + }, + [requestToken](nsresult rv) { + LOGD("RunAnalyzeRequestTask failed to get client"); + RefPtr owner = GetContentAnalysisFromService(); + if (!owner) { + + return; + } + owner->CancelWithError(std::move(requestToken), rv); + }); return rv; } @@ -722,6 +866,37 @@ ContentAnalysis::AnalyzeContentRequestCallback( return rv; } +NS_IMETHODIMP +ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) { + NS_DispatchToMainThread(NS_NewCancelableRunnableFunction( + "CancelContentAnalysisRequest", + [requestToken = nsCString{aRequestToken}]() { + RefPtr self = GetContentAnalysisFromService(); + if (!self) { + + return; + } + + auto callbackMap = self->mCallbackMap.Lock(); + auto entry = callbackMap->Lookup(requestToken); + LOGD("Content analysis cancelling request %s", requestToken.get()); + if (entry) { + entry->SetCanceled(); + RefPtr cancelResponse = + ContentAnalysisResponse::FromAction( + nsIContentAnalysisResponse::CANCELED, requestToken); + cancelResponse->SetOwner(self); + nsMainThreadPtrHandle callbackHolder = + entry->TakeCallbackHolder(); + callbackHolder->ContentResult(cancelResponse.get()); + } else { + LOGD("Content analysis request not found when trying to cancel %s", + requestToken.get()); + } + })); + return NS_OK; +} + NS_IMETHODIMP ContentAnalysisResponse::Acknowledge( nsIContentAnalysisAcknowledgement* aAcknowledgement) { @@ -747,24 +922,36 @@ nsresult ContentAnalysis::RunAcknowledgeTask( LogAcknowledgement(&pbAck); - - RefPtr owner = this; - - LOGD("RunAcknowledgeTask dispatching acknowledge task"); - return NS_DispatchBackgroundTask(NS_NewRunnableFunction( - "RunAcknowledgeTask", [owner, pbAck = std::move(pbAck)] { - auto caClientRef = sCaClient.Lock(); - auto& caClient = caClientRef.ref(); - if (!caClient) { - LOGD("RunAcknowledgeTask failed to get the client"); - return; - } - - DebugOnly err = caClient->Acknowledge(pbAck); - MOZ_ASSERT(err == 0); - LOGD("RunAcknowledgeTask sent transaction acknowledgement"); - })); + mCaClientPromise->Then( + GetCurrentSerialEventTarget(), __func__, + [pbAck = std::move(pbAck)]( + std::shared_ptr client) { + NS_DispatchBackgroundTask( + NS_NewCancelableRunnableFunction( + __func__, + [pbAck = std::move(pbAck), client] { + RefPtr owner = + GetContentAnalysisFromService(); + if (!owner) { + + return; + } + if (!client) { + return; + } + + int err = client->Acknowledge(pbAck); + MOZ_ASSERT(err == 0); + LOGD( + "RunAcknowledgeTask sent transaction acknowledgement, " + "err=%d", + err); + }), + NS_DISPATCH_EVENT_MAY_BLOCK); + }, + [](nsresult rv) { LOGD("RunAcknowledgeTask failed to get the client"); }); + return rv; } NS_IMETHODIMP ContentAnalysisCallback::ContentResult( @@ -789,4 +976,7 @@ NS_IMETHODIMP ContentAnalysisCallback::Error(nsresult aError) { ContentAnalysisCallback::ContentAnalysisCallback(RefPtr aPromise) : mPromise(Some(new nsMainThreadPtrHolder( "content analysis promise", aPromise))) {} + +#undef LOGD +#undef LOGE } diff --git a/toolkit/components/contentanalysis/ContentAnalysis.h b/toolkit/components/contentanalysis/ContentAnalysis.h index 0dbd8bf7b035..7daca3723481 100644 --- a/toolkit/components/contentanalysis/ContentAnalysis.h +++ b/toolkit/components/contentanalysis/ContentAnalysis.h @@ -8,11 +8,12 @@ #include "mozilla/DataMutex.h" #include "mozilla/dom/WindowGlobalParent.h" -#include "mozilla/Mutex.h" #include "nsIContentAnalysis.h" #include "nsProxyRelease.h" #include "nsString.h" +#include "nsTHashMap.h" +#include #include namespace content_analysis::sdk { @@ -73,30 +74,65 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest { RefPtr mWindowGlobalParent; }; +#define CONTENTANALYSIS_IID \ + { \ + 0xa37bed74, 0x4b50, 0x443a, { \ + 0xbf, 0x58, 0xf4, 0xeb, 0xbd, 0x30, 0x67, 0xb4 \ + } \ + } + class ContentAnalysisResponse; class ContentAnalysis final : public nsIContentAnalysis { public: + NS_DECLARE_STATIC_IID_ACCESSOR(CONTENTANALYSIS_IID) NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSICONTENTANALYSIS - ContentAnalysis() = default; + ContentAnalysis(); private: ~ContentAnalysis(); ContentAnalysis(const ContentAnalysis&) = delete; ContentAnalysis& operator=(ContentAnalysis&) = delete; - nsresult EnsureContentAnalysisClient(); + nsresult CreateContentAnalysisClient(nsCString&& aPipePathName, + bool aIsPerUser); nsresult RunAnalyzeRequestTask(RefPtr aRequest, RefPtr aCallback); nsresult RunAcknowledgeTask( nsIContentAnalysisAcknowledgement* aAcknowledgement, const nsACString& aRequestToken); - - static StaticDataMutex> sCaClient; + nsresult CancelWithError(nsCString aRequestToken, nsresult aResult); + static RefPtr GetContentAnalysisFromService(); + + using ClientPromise = + MozPromise, nsresult, + false>; + RefPtr mCaClientPromise; + + bool mClientCreationAttempted; + + class CallbackData { + public: + explicit CallbackData( + nsMainThreadPtrHandle&& aCallbackHolder) + : mCallbackHolder(aCallbackHolder) {} + + nsMainThreadPtrHandle TakeCallbackHolder() { + return std::move(mCallbackHolder); + } + void SetCanceled() { mCallbackHolder = nullptr; } + bool Canceled() const { return !mCallbackHolder; } + + private: + nsMainThreadPtrHandle mCallbackHolder; + }; + DataMutex> mCallbackMap; friend class ContentAnalysisResponse; }; +NS_DEFINE_STATIC_IID_ACCESSOR(ContentAnalysis, CONTENTANALYSIS_IID) + class ContentAnalysisResponse final : public nsIContentAnalysisResponse { public: NS_DECL_ISUPPORTS diff --git a/toolkit/components/contentanalysis/nsIContentAnalysis.idl b/toolkit/components/contentanalysis/nsIContentAnalysis.idl index 2d145eceba06..7f70d6a3d0f3 100644 --- a/toolkit/components/contentanalysis/nsIContentAnalysis.idl +++ b/toolkit/components/contentanalysis/nsIContentAnalysis.idl @@ -46,6 +46,7 @@ interface nsIContentAnalysisResponse : nsISupports const unsigned long ALLOW = 1000; + const unsigned long CANCELED = 1001; [infallible] readonly attribute unsigned long action; [infallible] readonly attribute boolean shouldAllowContent; @@ -162,6 +163,7 @@ interface nsIContentAnalysis : nsISupports + readonly attribute bool isActive; @@ -194,4 +196,11 @@ interface nsIContentAnalysis : nsISupports void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in nsIContentAnalysisCallback callback); + + + + + + + void cancelContentAnalysisRequest(in ACString aRequestToken); }; diff --git a/toolkit/components/prompts/content/commonDialog.css b/toolkit/components/prompts/content/commonDialog.css index 3521af13c6ed..ac01353aae42 100644 --- a/toolkit/components/prompts/content/commonDialog.css +++ b/toolkit/components/prompts/content/commonDialog.css @@ -58,6 +58,15 @@ dialog[insecureauth] { flex: 1; } +#spinnerContainer { + align-items: center; +} + +#spinnerContainer > img { + width: 16px; + height: 16px; +} + #loginLabel, #password1Label { text-align: start; } diff --git a/toolkit/components/prompts/content/commonDialog.js b/toolkit/components/prompts/content/commonDialog.js index 6afc3b87662d..83ea0ca89800 100644 --- a/toolkit/components/prompts/content/commonDialog.js +++ b/toolkit/components/prompts/content/commonDialog.js @@ -83,6 +83,7 @@ function commonDialogOnLoad() { infoBody: document.getElementById("infoBody"), infoTitle: document.getElementById("infoTitle"), infoIcon: document.getElementById("infoIcon"), + spinnerContainer: document.getElementById("spinnerContainer"), checkbox: document.getElementById("checkbox"), checkboxContainer: document.getElementById("checkboxContainer"), button3: dialog.getButton("extra2"), diff --git a/toolkit/components/prompts/content/commonDialog.xhtml b/toolkit/components/prompts/content/commonDialog.xhtml index 83cc37d9edb7..def3b93956ea 100644 --- a/toolkit/components/prompts/content/commonDialog.xhtml +++ b/toolkit/components/prompts/content/commonDialog.xhtml @@ -83,6 +83,16 @@ /> +