mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1875481 - Part 3: Add content analysis support to printing operations r=handyman,spohl,fluent-reviewers,kpatenio,smaug,emilio
Differential Revision: https://phabricator.services.mozilla.com/D200979
This commit is contained in:
parent
d0d35d1366
commit
b32aa334d7
@ -300,10 +300,10 @@ export const ContentAnalysis = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
const operation = request.analysisType;
|
||||
const analysisType = request.analysisType;
|
||||
// For operations that block browser interaction, show the "slow content analysis"
|
||||
// dialog faster
|
||||
let slowTimeoutMs = this._shouldShowBlockingNotification(operation)
|
||||
let slowTimeoutMs = this._shouldShowBlockingNotification(analysisType)
|
||||
? this._SLOW_DLP_NOTIFICATION_BLOCKING_TIMEOUT_MS
|
||||
: this._SLOW_DLP_NOTIFICATION_NONBLOCKING_TIMEOUT_MS;
|
||||
let browsingContext = request.windowGlobalParent?.browsingContext;
|
||||
@ -333,7 +333,7 @@ export const ContentAnalysis = {
|
||||
timer: lazy.setTimeout(() => {
|
||||
this.dlpBusyViewsByTopBrowsingContext.setEntry(browsingContext, {
|
||||
notification: this._showSlowCAMessage(
|
||||
operation,
|
||||
analysisType,
|
||||
request,
|
||||
resourceNameOrOperationType,
|
||||
browsingContext
|
||||
@ -450,7 +450,10 @@ export const ContentAnalysis = {
|
||||
}
|
||||
|
||||
if (this._SHOW_NOTIFICATIONS) {
|
||||
const notification = new aBrowsingContext.topChromeWindow.Notification(
|
||||
let topWindow =
|
||||
aBrowsingContext.topChromeWindow ??
|
||||
aBrowsingContext.embedderWindowGlobal.browsingContext.topChromeWindow;
|
||||
const notification = new topWindow.Notification(
|
||||
this.l10n.formatValueSync("contentanalysis-notification-title"),
|
||||
{
|
||||
body: aMessage,
|
||||
@ -469,10 +472,10 @@ export const ContentAnalysis = {
|
||||
return null;
|
||||
},
|
||||
|
||||
_shouldShowBlockingNotification(aOperation) {
|
||||
_shouldShowBlockingNotification(aAnalysisType) {
|
||||
return !(
|
||||
aOperation == Ci.nsIContentAnalysisRequest.eFileDownloaded ||
|
||||
aOperation == Ci.nsIContentAnalysisRequest.ePrint
|
||||
aAnalysisType == Ci.nsIContentAnalysisRequest.eFileDownloaded ||
|
||||
aAnalysisType == Ci.nsIContentAnalysisRequest.ePrint
|
||||
);
|
||||
},
|
||||
|
||||
@ -488,6 +491,9 @@ export const ContentAnalysis = {
|
||||
case Ci.nsIContentAnalysisRequest.eDroppedText:
|
||||
l10nId = "contentanalysis-operationtype-dropped-text";
|
||||
break;
|
||||
case Ci.nsIContentAnalysisRequest.eOperationPrint:
|
||||
l10nId = "contentanalysis-operationtype-print";
|
||||
break;
|
||||
}
|
||||
if (!l10nId) {
|
||||
console.error(
|
||||
@ -596,10 +602,14 @@ export const ContentAnalysis = {
|
||||
case Ci.nsIContentAnalysisRequest.eDroppedText:
|
||||
l10nId = "contentanalysis-slow-agent-dialog-body-dropped-text";
|
||||
break;
|
||||
case Ci.nsIContentAnalysisRequest.eOperationPrint:
|
||||
l10nId = "contentanalysis-slow-agent-dialog-body-print";
|
||||
break;
|
||||
}
|
||||
if (!l10nId) {
|
||||
console.error(
|
||||
"Unknown operationTypeForDisplay: " + aResourceNameOrOperationType
|
||||
"Unknown operationTypeForDisplay: ",
|
||||
aResourceNameOrOperationType
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
@ -6,8 +6,10 @@
|
||||
|
||||
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
||||
|
||||
#include "ContentAnalysis.h"
|
||||
#include "ErrorList.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
#include "mozilla/Components.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/EventForwards.h"
|
||||
#include "mozilla/AsyncEventDispatcher.h"
|
||||
@ -47,6 +49,7 @@
|
||||
#include "nsFrameLoader.h"
|
||||
#include "nsFrameLoaderOwner.h"
|
||||
#include "nsGlobalWindowOuter.h"
|
||||
#include "nsIContentAnalysis.h"
|
||||
#include "nsIWebBrowserChrome.h"
|
||||
#include "nsIXULRuntime.h"
|
||||
#include "nsNetUtil.h"
|
||||
@ -668,6 +671,9 @@ CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
|
||||
|
||||
using PrintPromise = CanonicalBrowsingContext::PrintPromise;
|
||||
#ifdef NS_PRINTING
|
||||
// Clients must call StaticCloneForPrintingCreated or
|
||||
// NoStaticCloneForPrintingWillBeCreated before the underlying promise can
|
||||
// resolve.
|
||||
class PrintListenerAdapter final : public nsIWebProgressListener {
|
||||
public:
|
||||
explicit PrintListenerAdapter(PrintPromise::Private* aPromise)
|
||||
@ -678,10 +684,14 @@ class PrintListenerAdapter final : public nsIWebProgressListener {
|
||||
// NS_DECL_NSIWEBPROGRESSLISTENER
|
||||
NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
|
||||
uint32_t aStateFlags, nsresult aStatus) override {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
|
||||
aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
|
||||
mPromise->Resolve(true, __func__);
|
||||
mPromise = nullptr;
|
||||
mPrintJobFinished = true;
|
||||
if (mHaveSetBrowsingContext) {
|
||||
mPromise->Resolve(mClonedStaticBrowsingContext, __func__);
|
||||
mPromise = nullptr;
|
||||
}
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
@ -716,10 +726,28 @@ class PrintListenerAdapter final : public nsIWebProgressListener {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void StaticCloneForPrintingCreated(
|
||||
MaybeDiscardedBrowsingContext&& aClonedStaticBrowsingContext) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
mClonedStaticBrowsingContext = std::move(aClonedStaticBrowsingContext);
|
||||
mHaveSetBrowsingContext = true;
|
||||
if (mPrintJobFinished && mPromise) {
|
||||
mPromise->Resolve(mClonedStaticBrowsingContext, __func__);
|
||||
mPromise = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void NoStaticCloneForPrintingWillBeCreated() {
|
||||
StaticCloneForPrintingCreated(nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
~PrintListenerAdapter() = default;
|
||||
|
||||
RefPtr<PrintPromise::Private> mPromise;
|
||||
MaybeDiscardedBrowsingContext mClonedStaticBrowsingContext = nullptr;
|
||||
bool mHaveSetBrowsingContext = false;
|
||||
bool mPrintJobFinished = false;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener)
|
||||
@ -735,7 +763,9 @@ already_AddRefed<Promise> CanonicalBrowsingContext::PrintJS(
|
||||
Print(aPrintSettings)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[promise](bool) { promise->MaybeResolveWithUndefined(); },
|
||||
[promise](MaybeDiscardedBrowsingContext) {
|
||||
promise->MaybeResolveWithUndefined();
|
||||
},
|
||||
[promise](nsresult aResult) { promise->MaybeReject(aResult); });
|
||||
return promise.forget();
|
||||
}
|
||||
@ -745,7 +775,72 @@ RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
|
||||
#ifndef NS_PRINTING
|
||||
return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
|
||||
#else
|
||||
// Content analysis is not supported on non-Windows platforms.
|
||||
# if defined(XP_WIN)
|
||||
bool needContentAnalysis = false;
|
||||
nsCOMPtr<nsIContentAnalysis> contentAnalysis =
|
||||
mozilla::components::nsIContentAnalysis::Service();
|
||||
Unused << NS_WARN_IF(!contentAnalysis);
|
||||
if (contentAnalysis) {
|
||||
nsresult rv = contentAnalysis->GetIsActive(&needContentAnalysis);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
}
|
||||
if (needContentAnalysis) {
|
||||
auto done = MakeRefPtr<PrintPromise::Private>(__func__);
|
||||
contentanalysis::ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed(
|
||||
this, aPrintSettings)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[done, aPrintSettings = RefPtr{aPrintSettings},
|
||||
self = RefPtr{this}](
|
||||
contentanalysis::ContentAnalysis::PrintAllowedResult aResponse)
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable {
|
||||
if (aResponse.mAllowed) {
|
||||
self->PrintWithNoContentAnalysis(
|
||||
aPrintSettings, false,
|
||||
aResponse.mCachedStaticDocumentBrowsingContext)
|
||||
->ChainTo(done.forget(), __func__);
|
||||
} else {
|
||||
// Since we are not doing the second print in this case,
|
||||
// release the clone that is no longer needed.
|
||||
self->ReleaseClonedPrint(
|
||||
aResponse.mCachedStaticDocumentBrowsingContext);
|
||||
done->Reject(NS_ERROR_CONTENT_BLOCKED, __func__);
|
||||
}
|
||||
},
|
||||
[done, self = RefPtr{this}](
|
||||
contentanalysis::ContentAnalysis::PrintAllowedError
|
||||
aErrorResponse) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA {
|
||||
// Since we are not doing the second print in this case, release
|
||||
// the clone that is no longer needed.
|
||||
self->ReleaseClonedPrint(
|
||||
aErrorResponse.mCachedStaticDocumentBrowsingContext);
|
||||
done->Reject(aErrorResponse.mError, __func__);
|
||||
});
|
||||
return done;
|
||||
}
|
||||
# endif
|
||||
return PrintWithNoContentAnalysis(aPrintSettings, false, nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CanonicalBrowsingContext::ReleaseClonedPrint(
|
||||
const MaybeDiscardedBrowsingContext& aClonedStaticBrowsingContext) {
|
||||
#ifdef NS_PRINTING
|
||||
auto* browserParent = GetBrowserParent();
|
||||
if (NS_WARN_IF(!browserParent)) {
|
||||
return;
|
||||
}
|
||||
Unused << browserParent->SendDestroyPrintClone(aClonedStaticBrowsingContext);
|
||||
#endif
|
||||
}
|
||||
|
||||
RefPtr<PrintPromise> CanonicalBrowsingContext::PrintWithNoContentAnalysis(
|
||||
nsIPrintSettings* aPrintSettings, bool aForceStaticDocument,
|
||||
const MaybeDiscardedBrowsingContext& aCachedStaticDocument) {
|
||||
#ifndef NS_PRINTING
|
||||
return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
|
||||
#else
|
||||
auto promise = MakeRefPtr<PrintPromise::Private>(__func__);
|
||||
auto listener = MakeRefPtr<PrintListenerAdapter>(promise);
|
||||
if (IsInProcess()) {
|
||||
@ -757,12 +852,14 @@ RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
|
||||
}
|
||||
|
||||
ErrorResult rv;
|
||||
listener->NoStaticCloneForPrintingWillBeCreated();
|
||||
outerWindow->Print(aPrintSettings,
|
||||
/* aRemotePrintJob = */ nullptr, listener,
|
||||
/* aDocShellToCloneInto = */ nullptr,
|
||||
nsGlobalWindowOuter::IsPreview::No,
|
||||
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
||||
/* aPrintPreviewCallback = */ nullptr, rv);
|
||||
/* aPrintPreviewCallback = */ nullptr,
|
||||
/* aCachedBrowsingContext = */ nullptr, rv);
|
||||
if (rv.Failed()) {
|
||||
promise->Reject(rv.StealNSResult(), __func__);
|
||||
}
|
||||
@ -805,12 +902,31 @@ RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
|
||||
printData.remotePrintJob() =
|
||||
browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
|
||||
|
||||
if (listener) {
|
||||
remotePrintJob->RegisterListener(listener);
|
||||
}
|
||||
remotePrintJob->RegisterListener(listener);
|
||||
|
||||
if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
|
||||
promise->Reject(NS_ERROR_FAILURE, __func__);
|
||||
if (!aCachedStaticDocument.IsNullOrDiscarded()) {
|
||||
// There is no cloned static browsing context that
|
||||
// SendPrintClonedPage() will return, so indicate this
|
||||
// so listener can resolve its promise.
|
||||
listener->NoStaticCloneForPrintingWillBeCreated();
|
||||
if (NS_WARN_IF(!browserParent->SendPrintClonedPage(
|
||||
this, printData, aCachedStaticDocument))) {
|
||||
promise->Reject(NS_ERROR_FAILURE, __func__);
|
||||
}
|
||||
} else {
|
||||
RefPtr<PBrowserParent::PrintPromise> printPromise =
|
||||
browserParent->SendPrint(this, printData, aForceStaticDocument);
|
||||
printPromise->Then(
|
||||
GetMainThreadSerialEventTarget(), __func__,
|
||||
[listener](MaybeDiscardedBrowsingContext cachedStaticDocument) {
|
||||
// promise will get resolved by the listener
|
||||
listener->StaticCloneForPrintingCreated(
|
||||
std::move(cachedStaticDocument));
|
||||
},
|
||||
[promise](ResponseRejectReason reason) {
|
||||
NS_WARNING("SendPrint() failed");
|
||||
promise->Reject(NS_ERROR_FAILURE, __func__);
|
||||
});
|
||||
}
|
||||
return promise.forget();
|
||||
#endif
|
||||
|
@ -136,11 +136,16 @@ class CanonicalBrowsingContext final : public BrowsingContext {
|
||||
UniquePtr<LoadingSessionHistoryInfo> ReplaceLoadingSessionHistoryEntryForLoad(
|
||||
LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel);
|
||||
|
||||
using PrintPromise = MozPromise</* unused */ bool, nsresult, false>;
|
||||
using PrintPromise =
|
||||
MozPromise<MaybeDiscardedBrowsingContext, nsresult, false>;
|
||||
MOZ_CAN_RUN_SCRIPT RefPtr<PrintPromise> Print(nsIPrintSettings*);
|
||||
MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PrintJS(nsIPrintSettings*,
|
||||
ErrorResult&);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT RefPtr<PrintPromise> PrintWithNoContentAnalysis(
|
||||
nsIPrintSettings* aPrintSettings, bool aForceStaticDocument,
|
||||
const MaybeDiscardedBrowsingContext& aClonedStaticBrowsingContext);
|
||||
MOZ_CAN_RUN_SCRIPT void ReleaseClonedPrint(
|
||||
const MaybeDiscardedBrowsingContext& aClonedStaticBrowsingContext);
|
||||
// Call the given callback on all top-level descendant BrowsingContexts.
|
||||
// Return Callstate::Stop from the callback to stop calling further children.
|
||||
//
|
||||
|
@ -3388,7 +3388,8 @@ already_AddRefed<Promise> nsFrameLoader::PrintPreview(
|
||||
/* aListener = */ nullptr, docShellToCloneInto,
|
||||
nsGlobalWindowOuter::IsPreview::Yes,
|
||||
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
||||
[resolve](const PrintPreviewResultInfo& aInfo) { resolve(aInfo); }, rv);
|
||||
[resolve](const PrintPreviewResultInfo& aInfo) { resolve(aInfo); },
|
||||
nullptr, rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
promise->MaybeReject(std::move(rv));
|
||||
}
|
||||
|
@ -3752,7 +3752,7 @@ Nullable<WindowProxyHolder> nsGlobalWindowInner::PrintPreview(
|
||||
/* aRemotePrintJob = */ nullptr, aListener, aDocShellToCloneInto,
|
||||
nsGlobalWindowOuter::IsPreview::Yes,
|
||||
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
||||
/* aPrintPreviewCallback = */ nullptr, aError),
|
||||
/* aPrintPreviewCallback = */ nullptr, nullptr, aError),
|
||||
aError, nullptr);
|
||||
}
|
||||
|
||||
|
@ -5006,7 +5006,7 @@ void nsGlobalWindowOuter::PrintOuter(ErrorResult& aError) {
|
||||
|
||||
const bool forPreview = !StaticPrefs::print_always_print_silent();
|
||||
Print(nullptr, nullptr, nullptr, nullptr, IsPreview(forPreview),
|
||||
IsForWindowDotPrint::Yes, nullptr, aError);
|
||||
IsForWindowDotPrint::Yes, nullptr, nullptr, aError);
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -5028,7 +5028,8 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
|
||||
nsIPrintSettings* aPrintSettings, RemotePrintJobChild* aRemotePrintJob,
|
||||
nsIWebProgressListener* aListener, nsIDocShell* aDocShellToCloneInto,
|
||||
IsPreview aIsPreview, IsForWindowDotPrint aForWindowDotPrint,
|
||||
PrintPreviewResolver&& aPrintPreviewCallback, ErrorResult& aError) {
|
||||
PrintPreviewResolver&& aPrintPreviewCallback,
|
||||
RefPtr<BrowsingContext>* aCachedBrowsingContext, ErrorResult& aError) {
|
||||
#ifdef NS_PRINTING
|
||||
nsCOMPtr<nsIPrintSettingsService> printSettingsService =
|
||||
do_GetService("@mozilla.org/gfx/printsettings-service;1");
|
||||
@ -5064,16 +5065,36 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
|
||||
nsCOMPtr<nsIDocumentViewer> viewer;
|
||||
RefPtr<BrowsingContext> bc;
|
||||
bool hasPrintCallbacks = false;
|
||||
if (docToPrint->IsStaticDocument()) {
|
||||
bool wasStaticDocument = docToPrint->IsStaticDocument();
|
||||
bool usingCachedBrowsingContext = false;
|
||||
if (aCachedBrowsingContext && *aCachedBrowsingContext) {
|
||||
MOZ_ASSERT(!wasStaticDocument,
|
||||
"Why pass in non-empty aCachedBrowsingContext if original "
|
||||
"document is already static?");
|
||||
if (!wasStaticDocument) {
|
||||
// The passed in document is not a static clone and the caller passed in a
|
||||
// static clone to reuse, so swap it in.
|
||||
docToPrint = (*aCachedBrowsingContext)->GetDocument();
|
||||
MOZ_ASSERT(docToPrint);
|
||||
MOZ_ASSERT(docToPrint->IsStaticDocument());
|
||||
wasStaticDocument = true;
|
||||
usingCachedBrowsingContext = true;
|
||||
}
|
||||
}
|
||||
if (wasStaticDocument) {
|
||||
if (aForWindowDotPrint == IsForWindowDotPrint::Yes) {
|
||||
aError.ThrowNotSupportedError(
|
||||
"Calling print() from a print preview is unsupported, did you intend "
|
||||
"to call printPreview() instead?");
|
||||
return nullptr;
|
||||
}
|
||||
// We're already a print preview window, just reuse our browsing context /
|
||||
// content viewer.
|
||||
bc = sourceBC;
|
||||
if (usingCachedBrowsingContext) {
|
||||
bc = docToPrint->GetBrowsingContext();
|
||||
} else {
|
||||
// We're already a print preview window, just reuse our browsing context /
|
||||
// content viewer.
|
||||
bc = sourceBC;
|
||||
}
|
||||
nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
|
||||
if (!docShell) {
|
||||
aError.ThrowNotSupportedError("No docshell");
|
||||
@ -5115,6 +5136,10 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
|
||||
if (NS_WARN_IF(aError.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
if (aCachedBrowsingContext) {
|
||||
MOZ_ASSERT(!*aCachedBrowsingContext);
|
||||
*aCachedBrowsingContext = bc;
|
||||
}
|
||||
}
|
||||
if (!bc) {
|
||||
aError.ThrowNotAllowedError("No browsing context");
|
||||
@ -5170,6 +5195,24 @@ Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
|
||||
"Content viewer didn't implement nsIWebBrowserPrint");
|
||||
return nullptr;
|
||||
}
|
||||
bool closeWindowAfterPrint;
|
||||
if (wasStaticDocument) {
|
||||
// Here the document was a static clone to begin with that this code did not
|
||||
// create, so we should not clean it up.
|
||||
// The exception is if we're using the passed-in aCachedBrowsingContext, in
|
||||
// which case this is the second print with this static document clone that
|
||||
// we created the first time through, and we are responsible for cleaning it
|
||||
// up.
|
||||
closeWindowAfterPrint = usingCachedBrowsingContext;
|
||||
} else {
|
||||
// In this case the document was not a static clone, so we made a static
|
||||
// clone for printing purposes and must clean it up after the print is done.
|
||||
// The exception is if aCachedBrowsingContext is non-NULL, meaning the
|
||||
// caller is intending to print this document again, so we need to defer the
|
||||
// cleanup until after the second print.
|
||||
closeWindowAfterPrint = !aCachedBrowsingContext;
|
||||
}
|
||||
webBrowserPrint->SetCloseWindowAfterPrint(closeWindowAfterPrint);
|
||||
|
||||
// For window.print(), we postpone making these calls until the round-trip to
|
||||
// the parent process (triggered by the OpenInternal call above) calls us
|
||||
|
@ -580,7 +580,8 @@ class nsGlobalWindowOuter final : public mozilla::dom::EventTarget,
|
||||
Print(nsIPrintSettings*,
|
||||
mozilla::layout::RemotePrintJobChild* aRemotePrintJob,
|
||||
nsIWebProgressListener*, nsIDocShell*, IsPreview, IsForWindowDotPrint,
|
||||
PrintPreviewResolver&&, mozilla::ErrorResult&);
|
||||
PrintPreviewResolver&&, RefPtr<mozilla::dom::BrowsingContext>*,
|
||||
mozilla::ErrorResult&);
|
||||
mozilla::dom::Selection* GetSelectionOuter();
|
||||
already_AddRefed<mozilla::dom::Selection> GetSelection() override;
|
||||
nsScreen* GetScreen();
|
||||
|
@ -2342,7 +2342,7 @@ mozilla::ipc::IPCResult BrowserChild::RecvPrintPreview(
|
||||
/* aListener = */ nullptr, docShellToCloneInto,
|
||||
nsGlobalWindowOuter::IsPreview::Yes,
|
||||
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
||||
std::move(aCallback), IgnoreErrors());
|
||||
std::move(aCallback), nullptr, IgnoreErrors());
|
||||
#endif
|
||||
return IPC_OK();
|
||||
}
|
||||
@ -2359,8 +2359,9 @@ mozilla::ipc::IPCResult BrowserChild::RecvExitPrintPreview() {
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserChild::RecvPrint(
|
||||
const MaybeDiscardedBrowsingContext& aBc, const PrintData& aPrintData) {
|
||||
mozilla::ipc::IPCResult BrowserChild::CommonPrint(
|
||||
const MaybeDiscardedBrowsingContext& aBc, const PrintData& aPrintData,
|
||||
RefPtr<BrowsingContext>* aCachedBrowsingContext) {
|
||||
#ifdef NS_PRINTING
|
||||
if (NS_WARN_IF(aBc.IsNullOrDiscarded())) {
|
||||
return IPC_OK();
|
||||
@ -2389,12 +2390,12 @@ mozilla::ipc::IPCResult BrowserChild::RecvPrint(
|
||||
IgnoredErrorResult rv;
|
||||
RefPtr printJob = static_cast<RemotePrintJobChild*>(
|
||||
aPrintData.remotePrintJob().AsChild());
|
||||
outerWindow->Print(printSettings, printJob,
|
||||
/* aListener = */ nullptr,
|
||||
/* aWindowToCloneInto = */ nullptr,
|
||||
nsGlobalWindowOuter::IsPreview::No,
|
||||
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
||||
/* aPrintPreviewCallback = */ nullptr, rv);
|
||||
outerWindow->Print(
|
||||
printSettings, printJob,
|
||||
/* aListener = */ nullptr,
|
||||
/* aWindowToCloneInto = */ nullptr, nsGlobalWindowOuter::IsPreview::No,
|
||||
nsGlobalWindowOuter::IsForWindowDotPrint::No,
|
||||
/* aPrintPreviewCallback = */ nullptr, aCachedBrowsingContext, rv);
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return IPC_OK();
|
||||
}
|
||||
@ -2403,6 +2404,49 @@ mozilla::ipc::IPCResult BrowserChild::RecvPrint(
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserChild::RecvPrint(
|
||||
const MaybeDiscardedBrowsingContext& aBc, const PrintData& aPrintData,
|
||||
bool aReturnStaticClone, PrintResolver&& aResolve) {
|
||||
#ifdef NS_PRINTING
|
||||
RefPtr<BrowsingContext> browsingContext;
|
||||
auto result = CommonPrint(aBc, aPrintData,
|
||||
aReturnStaticClone ? &browsingContext : nullptr);
|
||||
aResolve(browsingContext);
|
||||
return result;
|
||||
#else
|
||||
aResolve(nullptr);
|
||||
return IPC_OK();
|
||||
#endif
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserChild::RecvPrintClonedPage(
|
||||
const MaybeDiscardedBrowsingContext& aBc, const PrintData& aPrintData,
|
||||
const MaybeDiscardedBrowsingContext& aClonedBc) {
|
||||
#ifdef NS_PRINTING
|
||||
if (aClonedBc.IsNullOrDiscarded()) {
|
||||
return IPC_OK();
|
||||
}
|
||||
RefPtr<BrowsingContext> clonedBc = aClonedBc.get();
|
||||
return CommonPrint(aBc, aPrintData, &clonedBc);
|
||||
#else
|
||||
return IPC_OK();
|
||||
#endif
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserChild::RecvDestroyPrintClone(
|
||||
const MaybeDiscardedBrowsingContext& aCachedPage) {
|
||||
#ifdef NS_PRINTING
|
||||
if (aCachedPage) {
|
||||
RefPtr<nsPIDOMWindowOuter> window = aCachedPage->GetDOMWindow();
|
||||
if (NS_WARN_IF(!window)) {
|
||||
return IPC_OK();
|
||||
}
|
||||
window->Close();
|
||||
}
|
||||
#endif
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
mozilla::ipc::IPCResult BrowserChild::RecvUpdateNativeWindowHandle(
|
||||
const uintptr_t& aNewHandle) {
|
||||
#if defined(XP_WIN) && defined(ACCESSIBILITY)
|
||||
|
@ -507,7 +507,15 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
|
||||
mozilla::ipc::IPCResult RecvExitPrintPreview();
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvPrint(
|
||||
const MaybeDiscardedBrowsingContext&, const PrintData&);
|
||||
const MaybeDiscardedBrowsingContext&, const PrintData&, bool,
|
||||
PrintResolver&&);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY mozilla::ipc::IPCResult RecvPrintClonedPage(
|
||||
const MaybeDiscardedBrowsingContext&, const PrintData&,
|
||||
const MaybeDiscardedBrowsingContext&);
|
||||
|
||||
mozilla::ipc::IPCResult RecvDestroyPrintClone(
|
||||
const MaybeDiscardedBrowsingContext&);
|
||||
|
||||
mozilla::ipc::IPCResult RecvUpdateNativeWindowHandle(
|
||||
const uintptr_t& aNewHandle);
|
||||
@ -712,6 +720,11 @@ class BrowserChild final : public nsMessageManagerScriptExecutor,
|
||||
|
||||
void InternalSetDocShellIsActive(bool aIsActive);
|
||||
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
mozilla::ipc::IPCResult CommonPrint(
|
||||
const MaybeDiscardedBrowsingContext& aBc, const PrintData& aPrintData,
|
||||
RefPtr<BrowsingContext>* aCachedBrowsingContext);
|
||||
|
||||
bool CreateRemoteLayerManager(
|
||||
mozilla::layers::PCompositorBridgeChild* aCompositorChild);
|
||||
|
||||
|
@ -960,8 +960,33 @@ child:
|
||||
*
|
||||
* @param aBrowsingContext the browsing context to print.
|
||||
* @param aPrintData the serialized settings to print with
|
||||
* @param aReturnStaticClone If the document in aBrowsingContext is not a static clone, whether
|
||||
* to return the static document clone created.
|
||||
* Note that if you call this with true but do not later call PrintClonedPage(),
|
||||
* you must call DestroyPrintCache() to avoid leaks.
|
||||
*/
|
||||
async Print(MaybeDiscardedBrowsingContext aBC, PrintData aPrintData);
|
||||
async Print(MaybeDiscardedBrowsingContext aBC, PrintData aPrintData, bool aReturnStaticClone) returns(MaybeDiscardedBrowsingContext staticCloneBrowsingContext);
|
||||
|
||||
/**
|
||||
* Tell the child to print the passed in static clone browsing context with the given settings.
|
||||
*
|
||||
* @param aBrowsingContext the browsing context to print.
|
||||
* @param aPrintData the serialized settings to print with
|
||||
* @param aStaticCloneBrowsingContext The static clone of aBrowsingContext that
|
||||
* was created by an earlier call to Print(). This is the page that will actually be
|
||||
* printed.
|
||||
*/
|
||||
async PrintClonedPage(MaybeDiscardedBrowsingContext aBC, PrintData aPrintData, MaybeDiscardedBrowsingContext aStaticCloneBrowsingContext);
|
||||
|
||||
/**
|
||||
* Destroy the static document clone for printing, if present. See Print() for details.
|
||||
* For callers' simplicity, it is safe to call this method even if aStaticCloneBrowsingContext
|
||||
* is null or has already been discarded.
|
||||
*
|
||||
* @param aStaticCloneBrowsingContext The static clone that was created by
|
||||
* an earlier call to Print().
|
||||
*/
|
||||
async DestroyPrintClone(MaybeDiscardedBrowsingContext aStaticCloneBrowsingContext);
|
||||
|
||||
/**
|
||||
* Update the child with the tab's current top-level native window handle.
|
||||
|
@ -449,6 +449,7 @@ class nsDocumentViewer final : public nsIDocumentViewer,
|
||||
|
||||
#ifdef NS_PRINTING
|
||||
unsigned mClosingWhilePrinting : 1;
|
||||
unsigned mCloseWindowAfterPrint : 1;
|
||||
|
||||
# if NS_PRINT_PREVIEW
|
||||
RefPtr<nsPrintJob> mPrintJob;
|
||||
@ -520,6 +521,7 @@ nsDocumentViewer::nsDocumentViewer()
|
||||
mInPermitUnloadPrompt(false),
|
||||
#ifdef NS_PRINTING
|
||||
mClosingWhilePrinting(false),
|
||||
mCloseWindowAfterPrint(false),
|
||||
#endif // NS_PRINTING
|
||||
mReloadEncodingSource(kCharsetUninitialized),
|
||||
mReloadEncoding(nullptr),
|
||||
@ -3139,6 +3141,20 @@ nsDocumentViewer::GetDoingPrintPreview(bool* aDoingPrintPreview) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentViewer::GetCloseWindowAfterPrint(bool* aCloseWindowAfterPrint) {
|
||||
NS_ENSURE_ARG_POINTER(aCloseWindowAfterPrint);
|
||||
|
||||
*aCloseWindowAfterPrint = mCloseWindowAfterPrint;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentViewer::SetCloseWindowAfterPrint(bool aCloseWindowAfterPrint) {
|
||||
mCloseWindowAfterPrint = aCloseWindowAfterPrint;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocumentViewer::ExitPrintPreview() {
|
||||
NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
|
||||
@ -3301,15 +3317,23 @@ void nsDocumentViewer::OnDonePrinting() {
|
||||
printJob->Destroy();
|
||||
}
|
||||
|
||||
// We are done printing, now clean up.
|
||||
//
|
||||
// For non-print-preview jobs, we are actually responsible for cleaning up
|
||||
// our whole <browser> or window (see the OPEN_PRINT_BROWSER code), so gotta
|
||||
// run window.close(), which will take care of this.
|
||||
//
|
||||
// For print preview jobs the front-end code is responsible for cleaning the
|
||||
// UI.
|
||||
if (!printJob->CreatedForPrintPreview()) {
|
||||
// We are done printing, now clean up.
|
||||
//
|
||||
// If the original document to print was not a static clone, we opened a new
|
||||
// window and are responsible for cleaning up the whole <browser> or window
|
||||
// (see the OPEN_PRINT_BROWSER code, specifically
|
||||
// handleStaticCloneCreatedForPrint()), so gotta run window.close(), which
|
||||
// will take care of this.
|
||||
//
|
||||
// Otherwise the front-end code is responsible for cleaning the UI.
|
||||
# ifdef ANDROID
|
||||
// Android doesn't support Content Analysis and prints in a different way,
|
||||
// so use different logic to clean up.
|
||||
bool closeWindowAfterPrint = !printJob->CreatedForPrintPreview();
|
||||
# else
|
||||
bool closeWindowAfterPrint = GetCloseWindowAfterPrint();
|
||||
# endif
|
||||
if (closeWindowAfterPrint) {
|
||||
if (mContainer) {
|
||||
if (nsCOMPtr<nsPIDOMWindowOuter> win = mContainer->GetWindow()) {
|
||||
win->Close();
|
||||
|
@ -30,7 +30,7 @@ native PrintPreviewResolver(std::function<void(const mozilla::dom::PrintPreviewR
|
||||
* nsIWebBrowserPrint corresponds to the main interface
|
||||
* for printing an embedded Gecko web browser window/document
|
||||
*/
|
||||
[scriptable, uuid(c9a934ed-fff1-4971-bfba-6c25ad70e1e6)]
|
||||
[scriptable, builtinclass, uuid(c9a934ed-fff1-4971-bfba-6c25ad70e1e6)]
|
||||
interface nsIWebBrowserPrint : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -85,6 +85,13 @@ interface nsIWebBrowserPrint : nsISupports
|
||||
*/
|
||||
readonly attribute long printPreviewCurrentPageNumber;
|
||||
|
||||
/*
|
||||
* Whether the document to print needs to have its window closed after printing
|
||||
* is done - see OPEN_PRINT_BROWSER and specifically handleStaticCloneCreatedForPrint().
|
||||
* This is set if the document is not a static clone.
|
||||
*/
|
||||
[infallible] attribute boolean closeWindowAfterPrint;
|
||||
|
||||
/**
|
||||
* Print the specified DOM window
|
||||
*
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "base/process_util.h"
|
||||
#include "GMPUtils.h" // ToHexString
|
||||
#include "mozilla/Components.h"
|
||||
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/WindowGlobalParent.h"
|
||||
#include "mozilla/Logging.h"
|
||||
@ -24,6 +25,9 @@
|
||||
#include "nsIFile.h"
|
||||
#include "nsIGlobalObject.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsIOutputStream.h"
|
||||
#include "nsIPrintSettings.h"
|
||||
#include "nsIStorageStream.h"
|
||||
#include "ScopedNSSTypes.h"
|
||||
#include "xpcpublic.h"
|
||||
|
||||
@ -35,6 +39,7 @@
|
||||
# include <windows.h>
|
||||
# define SECURITY_WIN32 1
|
||||
# include <security.h>
|
||||
# include "mozilla/NativeNt.h"
|
||||
# include "mozilla/WinDllServices.h"
|
||||
#endif // XP_WIN
|
||||
|
||||
@ -114,6 +119,11 @@ nsIContentAnalysisAcknowledgement::FinalAction ConvertResult(
|
||||
} // anonymous namespace
|
||||
|
||||
namespace mozilla::contentanalysis {
|
||||
ContentAnalysisRequest::~ContentAnalysisRequest() {
|
||||
#ifdef XP_WIN
|
||||
CloseHandle(mPrintDataHandle);
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentAnalysisRequest::GetAnalysisType(AnalysisType* aAnalysisType) {
|
||||
@ -133,6 +143,34 @@ ContentAnalysisRequest::GetFilePath(nsAString& aFilePath) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentAnalysisRequest::GetPrintDataHandle(uint64_t* aPrintDataHandle) {
|
||||
#ifdef XP_WIN
|
||||
uintptr_t printDataHandle = reinterpret_cast<uintptr_t>(mPrintDataHandle);
|
||||
uint64_t printDataValue = static_cast<uint64_t>(printDataHandle);
|
||||
*aPrintDataHandle = printDataValue;
|
||||
return NS_OK;
|
||||
#else
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentAnalysisRequest::GetPrinterName(nsAString& aPrinterName) {
|
||||
aPrinterName = mPrinterName;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentAnalysisRequest::GetPrintDataSize(uint64_t* aPrintDataSize) {
|
||||
#ifdef XP_WIN
|
||||
*aPrintDataSize = mPrintDataSize;
|
||||
return NS_OK;
|
||||
#else
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentAnalysisRequest::GetUrl(nsIURI** aUrl) {
|
||||
NS_ENSURE_ARG_POINTER(aUrl);
|
||||
@ -234,6 +272,8 @@ ContentAnalysisRequest::ContentAnalysisRequest(
|
||||
mUrl(std::move(aUrl)),
|
||||
mSha256Digest(std::move(aSha256Digest)),
|
||||
mWindowGlobalParent(aWindowGlobalParent) {
|
||||
MOZ_ASSERT(aAnalysisType != AnalysisType::ePrint,
|
||||
"Print should use other ContentAnalysisRequest constructor!");
|
||||
if (aStringIsFilePath) {
|
||||
mFilePath = std::move(aString);
|
||||
} else {
|
||||
@ -251,6 +291,32 @@ ContentAnalysisRequest::ContentAnalysisRequest(
|
||||
mRequestToken = GenerateRequestToken();
|
||||
}
|
||||
|
||||
ContentAnalysisRequest::ContentAnalysisRequest(
|
||||
const nsTArray<uint8_t> aPrintData, nsCOMPtr<nsIURI> aUrl,
|
||||
nsString aPrinterName, dom::WindowGlobalParent* aWindowGlobalParent)
|
||||
: mAnalysisType(AnalysisType::ePrint),
|
||||
mUrl(std::move(aUrl)),
|
||||
mPrinterName(std::move(aPrinterName)),
|
||||
mWindowGlobalParent(aWindowGlobalParent) {
|
||||
#ifdef XP_WIN
|
||||
LARGE_INTEGER dataContentLength;
|
||||
dataContentLength.QuadPart = static_cast<LONGLONG>(aPrintData.Length());
|
||||
mPrintDataHandle = ::CreateFileMappingW(
|
||||
INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, dataContentLength.HighPart,
|
||||
dataContentLength.LowPart, nullptr);
|
||||
if (mPrintDataHandle) {
|
||||
mozilla::nt::AutoMappedView view(mPrintDataHandle, FILE_MAP_ALL_ACCESS);
|
||||
memcpy(view.as<uint8_t>(), aPrintData.Elements(), aPrintData.Length());
|
||||
mPrintDataSize = aPrintData.Length();
|
||||
}
|
||||
#else
|
||||
MOZ_ASSERT_UNREACHABLE(
|
||||
"Content Analysis is not supported on non-Windows platforms");
|
||||
#endif
|
||||
mOperationTypeForDisplay = OperationType::eOperationPrint;
|
||||
mRequestToken = GenerateRequestToken();
|
||||
}
|
||||
|
||||
nsresult ContentAnalysisRequest::GetFileDigest(const nsAString& aFilePath,
|
||||
nsCString& aDigestString) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(
|
||||
@ -366,22 +432,44 @@ static nsresult ConvertToProtobuf(
|
||||
requestData->set_digest(sha256Digest.get());
|
||||
}
|
||||
|
||||
nsString filePath;
|
||||
rv = aIn->GetFilePath(filePath);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!filePath.IsEmpty()) {
|
||||
std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get();
|
||||
aOut->set_file_path(filePathStr);
|
||||
auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1);
|
||||
if (!filename.empty()) {
|
||||
requestData->set_filename(filename);
|
||||
if (analysisType == nsIContentAnalysisRequest::AnalysisType::ePrint) {
|
||||
#if XP_WIN
|
||||
uint64_t printDataHandle;
|
||||
MOZ_TRY(aIn->GetPrintDataHandle(&printDataHandle));
|
||||
if (!printDataHandle) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
aOut->mutable_print_data()->set_handle(printDataHandle);
|
||||
|
||||
uint64_t printDataSize;
|
||||
MOZ_TRY(aIn->GetPrintDataSize(&printDataSize));
|
||||
aOut->mutable_print_data()->set_size(printDataSize);
|
||||
|
||||
nsString printerName;
|
||||
MOZ_TRY(aIn->GetPrinterName(printerName));
|
||||
requestData->mutable_print_metadata()->set_printer_name(
|
||||
NS_ConvertUTF16toUTF8(printerName).get());
|
||||
#else
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
#endif
|
||||
} else {
|
||||
nsString textContent;
|
||||
rv = aIn->GetTextContent(textContent);
|
||||
nsString filePath;
|
||||
rv = aIn->GetFilePath(filePath);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
MOZ_ASSERT(!textContent.IsEmpty());
|
||||
aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get());
|
||||
if (!filePath.IsEmpty()) {
|
||||
std::string filePathStr = NS_ConvertUTF16toUTF8(filePath).get();
|
||||
aOut->set_file_path(filePathStr);
|
||||
auto filename = filePathStr.substr(filePathStr.find_last_of("/\\") + 1);
|
||||
if (!filename.empty()) {
|
||||
requestData->set_filename(filename);
|
||||
}
|
||||
} else {
|
||||
nsString textContent;
|
||||
rv = aIn->GetTextContent(textContent);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
MOZ_ASSERT(!textContent.IsEmpty());
|
||||
aOut->set_text_content(NS_ConvertUTF16toUTF8(textContent).get());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef XP_WIN
|
||||
@ -1409,6 +1497,181 @@ ContentAnalysis::RespondToWarnDialog(const nsACString& aRequestToken,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#if defined(XP_WIN)
|
||||
RefPtr<ContentAnalysis::PrintAllowedPromise>
|
||||
ContentAnalysis::PrintToPDFToDetermineIfPrintAllowed(
|
||||
dom::CanonicalBrowsingContext* aBrowsingContext,
|
||||
nsIPrintSettings* aPrintSettings) {
|
||||
// Note that the IsChrome() check here excludes a few
|
||||
// common about pages like about:config, about:preferences,
|
||||
// and about:support, but other about: pages may still
|
||||
// go through content analysis.
|
||||
if (aBrowsingContext->IsChrome()) {
|
||||
return PrintAllowedPromise::CreateAndResolve(PrintAllowedResult(true),
|
||||
__func__);
|
||||
}
|
||||
nsCOMPtr<nsIPrintSettings> contentAnalysisPrintSettings;
|
||||
if (NS_WARN_IF(NS_FAILED(aPrintSettings->Clone(
|
||||
getter_AddRefs(contentAnalysisPrintSettings)))) ||
|
||||
NS_WARN_IF(!aBrowsingContext->GetCurrentWindowGlobal())) {
|
||||
return PrintAllowedPromise::CreateAndReject(
|
||||
PrintAllowedError(NS_ERROR_FAILURE), __func__);
|
||||
}
|
||||
contentAnalysisPrintSettings->SetOutputDestination(
|
||||
nsIPrintSettings::OutputDestinationType::kOutputDestinationStream);
|
||||
contentAnalysisPrintSettings->SetOutputFormat(
|
||||
nsIPrintSettings::kOutputFormatPDF);
|
||||
nsCOMPtr<nsIStorageStream> storageStream =
|
||||
do_CreateInstance("@mozilla.org/storagestream;1");
|
||||
if (!storageStream) {
|
||||
return PrintAllowedPromise::CreateAndReject(
|
||||
PrintAllowedError(NS_ERROR_FAILURE), __func__);
|
||||
}
|
||||
// Use segment size of 512K
|
||||
nsresult rv = storageStream->Init(0x80000, UINT32_MAX);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return PrintAllowedPromise::CreateAndReject(PrintAllowedError(rv),
|
||||
__func__);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
storageStream->QueryInterface(NS_GET_IID(nsIOutputStream),
|
||||
getter_AddRefs(outputStream));
|
||||
MOZ_ASSERT(outputStream);
|
||||
|
||||
contentAnalysisPrintSettings->SetOutputStream(outputStream.get());
|
||||
RefPtr<dom::CanonicalBrowsingContext> browsingContext = aBrowsingContext;
|
||||
auto promise = MakeRefPtr<PrintAllowedPromise::Private>(__func__);
|
||||
nsCOMPtr<nsIPrintSettings> finalPrintSettings(aPrintSettings);
|
||||
aBrowsingContext
|
||||
->PrintWithNoContentAnalysis(contentAnalysisPrintSettings, true, nullptr)
|
||||
->Then(
|
||||
GetCurrentSerialEventTarget(), __func__,
|
||||
[browsingContext, contentAnalysisPrintSettings, finalPrintSettings,
|
||||
promise](
|
||||
dom::MaybeDiscardedBrowsingContext cachedStaticBrowsingContext)
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable {
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
contentAnalysisPrintSettings->GetOutputStream(
|
||||
getter_AddRefs(outputStream));
|
||||
nsCOMPtr<nsIStorageStream> storageStream =
|
||||
do_QueryInterface(outputStream);
|
||||
MOZ_ASSERT(storageStream);
|
||||
nsTArray<uint8_t> printData;
|
||||
uint32_t length = 0;
|
||||
storageStream->GetLength(&length);
|
||||
if (!printData.SetLength(length, fallible)) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(NS_ERROR_OUT_OF_MEMORY,
|
||||
cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
nsresult rv = storageStream->NewInputStream(
|
||||
0, getter_AddRefs(inputStream));
|
||||
if (NS_FAILED(rv)) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(rv, cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
uint32_t currentPosition = 0;
|
||||
while (currentPosition < length) {
|
||||
uint32_t elementsRead = 0;
|
||||
// Make sure the reinterpret_cast<> below is safe
|
||||
static_assert(std::is_trivially_assignable_v<
|
||||
decltype(*printData.Elements()), char>);
|
||||
rv = inputStream->Read(
|
||||
reinterpret_cast<char*>(printData.Elements()) +
|
||||
currentPosition,
|
||||
length - currentPosition, &elementsRead);
|
||||
if (NS_WARN_IF(NS_FAILED(rv) || !elementsRead)) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(NS_FAILED(rv) ? rv : NS_ERROR_FAILURE,
|
||||
cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
currentPosition += elementsRead;
|
||||
}
|
||||
|
||||
nsString printerName;
|
||||
rv = contentAnalysisPrintSettings->GetPrinterName(printerName);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(rv, cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* windowParent = browsingContext->GetCurrentWindowGlobal();
|
||||
if (!windowParent) {
|
||||
// The print window may have been closed by the user by now.
|
||||
// Cancel the print.
|
||||
promise->Reject(
|
||||
PrintAllowedError(NS_ERROR_ABORT,
|
||||
cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
nsCOMPtr<nsIURI> uri = windowParent->GetDocumentURI();
|
||||
nsCOMPtr<nsIContentAnalysisRequest> contentAnalysisRequest =
|
||||
new contentanalysis::ContentAnalysisRequest(
|
||||
std::move(printData), std::move(uri),
|
||||
std::move(printerName), windowParent);
|
||||
auto callback =
|
||||
MakeRefPtr<contentanalysis::ContentAnalysisCallback>(
|
||||
[browsingContext, cachedStaticBrowsingContext, promise,
|
||||
finalPrintSettings = std::move(finalPrintSettings)](
|
||||
nsIContentAnalysisResponse* aResponse)
|
||||
MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA mutable {
|
||||
bool shouldAllow = false;
|
||||
DebugOnly<nsresult> rv =
|
||||
aResponse->GetShouldAllowContent(
|
||||
&shouldAllow);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
promise->Resolve(
|
||||
PrintAllowedResult(
|
||||
shouldAllow, cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
},
|
||||
[promise,
|
||||
cachedStaticBrowsingContext](nsresult aError) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(aError,
|
||||
cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
});
|
||||
nsCOMPtr<nsIContentAnalysis> contentAnalysis =
|
||||
mozilla::components::nsIContentAnalysis::Service();
|
||||
if (NS_WARN_IF(!contentAnalysis)) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(rv, cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
} else {
|
||||
bool isActive = false;
|
||||
nsresult rv = contentAnalysis->GetIsActive(&isActive);
|
||||
// Should not be called if content analysis is not active
|
||||
MOZ_ASSERT(isActive);
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
rv = contentAnalysis->AnalyzeContentRequestCallback(
|
||||
contentAnalysisRequest, /* aAutoAcknowledge */ true,
|
||||
callback);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->Reject(
|
||||
PrintAllowedError(rv, cachedStaticBrowsingContext),
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
},
|
||||
[promise](nsresult aError) {
|
||||
promise->Reject(PrintAllowedError(aError), __func__);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
#endif
|
||||
|
||||
NS_IMETHODIMP
|
||||
ContentAnalysisResponse::Acknowledge(
|
||||
nsIContentAnalysisAcknowledgement* aAcknowledgement) {
|
||||
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "mozilla/DataMutex.h"
|
||||
#include "mozilla/MozPromise.h"
|
||||
#include "mozilla/dom/BrowsingContext.h"
|
||||
#include "mozilla/dom/MaybeDiscarded.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "nsIContentAnalysis.h"
|
||||
#include "nsProxyRelease.h"
|
||||
@ -18,10 +20,16 @@
|
||||
#include <regex>
|
||||
#include <string>
|
||||
|
||||
#ifdef XP_WIN
|
||||
# include <windows.h>
|
||||
#endif // XP_WIN
|
||||
|
||||
class nsIPrincipal;
|
||||
class nsIPrintSettings;
|
||||
class ContentAnalysisTest;
|
||||
|
||||
namespace mozilla::dom {
|
||||
class CanonicalBrowsingContext;
|
||||
class DataTransfer;
|
||||
class WindowGlobalParent;
|
||||
} // namespace mozilla::dom
|
||||
@ -64,11 +72,15 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
|
||||
bool aStringIsFilePath, nsCString aSha256Digest,
|
||||
nsCOMPtr<nsIURI> aUrl, OperationType aOperationType,
|
||||
dom::WindowGlobalParent* aWindowGlobalParent);
|
||||
ContentAnalysisRequest(const nsTArray<uint8_t> aPrintData,
|
||||
nsCOMPtr<nsIURI> aUrl, nsString aPrinterName,
|
||||
dom::WindowGlobalParent* aWindowGlobalParent);
|
||||
static nsresult GetFileDigest(const nsAString& aFilePath,
|
||||
nsCString& aDigestString);
|
||||
|
||||
private:
|
||||
~ContentAnalysisRequest() = default;
|
||||
~ContentAnalysisRequest();
|
||||
|
||||
// Remove unneeded copy constructor/assignment
|
||||
ContentAnalysisRequest(const ContentAnalysisRequest&) = delete;
|
||||
ContentAnalysisRequest& operator=(ContentAnalysisRequest&) = delete;
|
||||
@ -105,7 +117,16 @@ class ContentAnalysisRequest final : public nsIContentAnalysisRequest {
|
||||
// OPERATION_CUSTOMDISPLAYSTRING
|
||||
nsString mOperationDisplayString;
|
||||
|
||||
// The name of the printer being printed to
|
||||
nsString mPrinterName;
|
||||
|
||||
RefPtr<dom::WindowGlobalParent> mWindowGlobalParent;
|
||||
#ifdef XP_WIN
|
||||
// The printed data to analyze, in PDF format
|
||||
HANDLE mPrintDataHandle = 0;
|
||||
// The size of the printed data in mPrintDataHandle
|
||||
uint64_t mPrintDataSize = 0;
|
||||
#endif
|
||||
|
||||
friend class ::ContentAnalysisTest;
|
||||
};
|
||||
@ -128,6 +149,39 @@ class ContentAnalysis final : public nsIContentAnalysis {
|
||||
nsCString GetUserActionId();
|
||||
void SetLastResult(nsresult aLastResult) { mLastResult = aLastResult; }
|
||||
|
||||
struct PrintAllowedResult final {
|
||||
bool mAllowed;
|
||||
dom::MaybeDiscarded<dom::BrowsingContext>
|
||||
mCachedStaticDocumentBrowsingContext;
|
||||
PrintAllowedResult(bool aAllowed, dom::MaybeDiscarded<dom::BrowsingContext>
|
||||
aCachedStaticDocumentBrowsingContext)
|
||||
: mAllowed(aAllowed),
|
||||
mCachedStaticDocumentBrowsingContext(
|
||||
aCachedStaticDocumentBrowsingContext) {}
|
||||
explicit PrintAllowedResult(bool aAllowed)
|
||||
: PrintAllowedResult(aAllowed, dom::MaybeDiscardedBrowsingContext()) {}
|
||||
};
|
||||
struct PrintAllowedError final {
|
||||
nsresult mError;
|
||||
dom::MaybeDiscarded<dom::BrowsingContext>
|
||||
mCachedStaticDocumentBrowsingContext;
|
||||
PrintAllowedError(nsresult aError, dom::MaybeDiscarded<dom::BrowsingContext>
|
||||
aCachedStaticDocumentBrowsingContext)
|
||||
: mError(aError),
|
||||
mCachedStaticDocumentBrowsingContext(
|
||||
aCachedStaticDocumentBrowsingContext) {}
|
||||
explicit PrintAllowedError(nsresult aError)
|
||||
: PrintAllowedError(aError, dom::MaybeDiscardedBrowsingContext()) {}
|
||||
};
|
||||
using PrintAllowedPromise =
|
||||
MozPromise<PrintAllowedResult, PrintAllowedError, true>;
|
||||
#if defined(XP_WIN)
|
||||
MOZ_CAN_RUN_SCRIPT static RefPtr<PrintAllowedPromise>
|
||||
PrintToPDFToDetermineIfPrintAllowed(
|
||||
dom::CanonicalBrowsingContext* aBrowsingContext,
|
||||
nsIPrintSettings* aPrintSettings);
|
||||
#endif // defined(XP_WIN)
|
||||
|
||||
private:
|
||||
~ContentAnalysis();
|
||||
// Remove unneeded copy constructor/assignment
|
||||
|
@ -11,5 +11,6 @@ Classes = [
|
||||
'contract_ids': ['@mozilla.org/contentanalysis;1'],
|
||||
'type': 'mozilla::contentanalysis::ContentAnalysis',
|
||||
'headers': ['/toolkit/components/contentanalysis/ContentAnalysis.h'],
|
||||
'overridable': True,
|
||||
},
|
||||
]
|
||||
|
@ -131,6 +131,7 @@ interface nsIContentAnalysisRequest : nsISupports
|
||||
eCustomDisplayString = 0,
|
||||
eClipboard = 1,
|
||||
eDroppedText = 2,
|
||||
eOperationPrint = 3,
|
||||
};
|
||||
readonly attribute nsIContentAnalysisRequest_OperationType operationTypeForDisplay;
|
||||
readonly attribute AString operationDisplayString;
|
||||
@ -141,6 +142,15 @@ interface nsIContentAnalysisRequest : nsISupports
|
||||
// Name of file to analyze. Only one of textContent or filePath is defined.
|
||||
readonly attribute AString filePath;
|
||||
|
||||
// HANDLE to the printed data in PDF format.
|
||||
readonly attribute unsigned long long printDataHandle;
|
||||
|
||||
// Size of the data stored in printDataHandle.
|
||||
readonly attribute unsigned long long printDataSize;
|
||||
|
||||
// Name of the printer being printed to.
|
||||
readonly attribute AString printerName;
|
||||
|
||||
// The URL containing the file download/upload or to which web content is
|
||||
// being uploaded.
|
||||
readonly attribute nsIURI url;
|
||||
|
@ -1,3 +1,20 @@
|
||||
[DEFAULT]
|
||||
run-if = ["os == 'win'"]
|
||||
support-files = [
|
||||
"head.js",
|
||||
]
|
||||
|
||||
["browser_content_analysis_policies.js"]
|
||||
|
||||
["browser_print_changing_page_content_analysis.js"]
|
||||
support-files = [
|
||||
"!/toolkit/components/printing/tests/head.js",
|
||||
"changing_page_for_print.html",
|
||||
]
|
||||
|
||||
["browser_print_content_analysis.js"]
|
||||
support-files = [
|
||||
"!/toolkit/components/printing/tests/head.js",
|
||||
"!/toolkit/components/printing/tests/longerArticle.html",
|
||||
"!/toolkit/components/printing/tests/simplifyArticleSample.html",
|
||||
]
|
||||
|
@ -0,0 +1,339 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js",
|
||||
this
|
||||
);
|
||||
|
||||
const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
|
||||
Ci.nsIPrintSettingsService
|
||||
);
|
||||
|
||||
let mockCA = {
|
||||
isActive: true,
|
||||
mightBeActive: true,
|
||||
errorValue: undefined,
|
||||
|
||||
setupForTest(shouldAllowRequest) {
|
||||
this.shouldAllowRequest = shouldAllowRequest;
|
||||
this.errorValue = undefined;
|
||||
this.calls = [];
|
||||
},
|
||||
|
||||
setupForTestWithError(errorValue) {
|
||||
this.errorValue = errorValue;
|
||||
this.calls = [];
|
||||
},
|
||||
|
||||
getAction() {
|
||||
if (this.shouldAllowRequest === undefined) {
|
||||
this.shouldAllowRequest = true;
|
||||
}
|
||||
return this.shouldAllowRequest
|
||||
? Ci.nsIContentAnalysisResponse.eAllow
|
||||
: Ci.nsIContentAnalysisResponse.eBlock;
|
||||
},
|
||||
|
||||
// nsIContentAnalysis methods
|
||||
async analyzeContentRequest(request, _autoAcknowledge) {
|
||||
info(
|
||||
"Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" +
|
||||
this.shouldAllowRequest +
|
||||
", this.errorValue=" +
|
||||
this.errorValue
|
||||
);
|
||||
this.calls.push(request);
|
||||
if (this.errorValue) {
|
||||
throw this.errorValue;
|
||||
}
|
||||
// Use setTimeout to simulate an async activity
|
||||
await new Promise(res => setTimeout(res, 0));
|
||||
return makeContentAnalysisResponse(this.getAction(), request.requestToken);
|
||||
},
|
||||
|
||||
analyzeContentRequestCallback(request, autoAcknowledge, callback) {
|
||||
info(
|
||||
"Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" +
|
||||
this.shouldAllowRequest +
|
||||
", this.errorValue=" +
|
||||
this.errorValue
|
||||
);
|
||||
this.calls.push(request);
|
||||
if (this.errorValue) {
|
||||
throw this.errorValue;
|
||||
}
|
||||
let response = makeContentAnalysisResponse(
|
||||
this.getAction(),
|
||||
request.requestToken
|
||||
);
|
||||
// Use setTimeout to simulate an async activity
|
||||
setTimeout(() => {
|
||||
callback.contentResult(response);
|
||||
}, 0);
|
||||
},
|
||||
};
|
||||
|
||||
add_setup(async function test_setup() {
|
||||
mockCA = mockContentAnalysisService(mockCA);
|
||||
});
|
||||
|
||||
const TEST_PAGE_URL = PrintHelper.getTestPageUrlHTTPS(
|
||||
"changing_page_for_print.html"
|
||||
);
|
||||
|
||||
function addUniqueSuffix(prefix) {
|
||||
return `${prefix}-${Services.uuid
|
||||
.generateUUID()
|
||||
.toString()
|
||||
.slice(1, -1)}.pdf`;
|
||||
}
|
||||
|
||||
async function printToDestination(aBrowser, aDestination) {
|
||||
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`);
|
||||
let filePath = PathUtils.join(tmpDir.path, fileName);
|
||||
|
||||
info(`Printing to ${filePath}`);
|
||||
|
||||
let settings = PSSVC.createNewPrintSettings();
|
||||
settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
|
||||
settings.outputDestination = aDestination;
|
||||
|
||||
settings.headerStrCenter = "";
|
||||
settings.headerStrLeft = "";
|
||||
settings.headerStrRight = "";
|
||||
settings.footerStrCenter = "";
|
||||
settings.footerStrLeft = "";
|
||||
settings.footerStrRight = "";
|
||||
|
||||
settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */
|
||||
let outStream = null;
|
||||
if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) {
|
||||
settings.toFileName = PathUtils.join(tmpDir.path, fileName);
|
||||
} else {
|
||||
is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream);
|
||||
outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
|
||||
Ci.nsIFileOutputStream
|
||||
);
|
||||
let tmpFile = tmpDir.clone();
|
||||
tmpFile.append(fileName);
|
||||
outStream.init(tmpFile, -1, 0o666, 0);
|
||||
settings.outputStream = outStream;
|
||||
}
|
||||
|
||||
await aBrowser.browsingContext.print(settings);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function assertContentAnalysisRequest(request) {
|
||||
is(request.url.spec, TEST_PAGE_URL, "request has correct URL");
|
||||
is(
|
||||
request.analysisType,
|
||||
Ci.nsIContentAnalysisRequest.ePrint,
|
||||
"request has print analysisType"
|
||||
);
|
||||
is(
|
||||
request.operationTypeForDisplay,
|
||||
Ci.nsIContentAnalysisRequest.eOperationPrint,
|
||||
"request has print operationTypeForDisplay"
|
||||
);
|
||||
is(request.textContent, "", "request textContent should be empty");
|
||||
is(request.filePath, "", "request filePath should be empty");
|
||||
isnot(request.printDataHandle, 0, "request printDataHandle should not be 0");
|
||||
isnot(request.printDataSize, 0, "request printDataSize should not be 0");
|
||||
ok(!!request.requestToken.length, "request requestToken should not be empty");
|
||||
}
|
||||
|
||||
add_task(
|
||||
async function testPrintToStreamWithContentAnalysisActiveAndAllowing() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(true);
|
||||
|
||||
let filePath = await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
|
||||
// This effectively tests that the PDF content sent to Content Analysis
|
||||
// and the content that is actually printed matches. This is necessary
|
||||
// because a previous iteration of the Content Analysis code didn't use
|
||||
// a static Document clone for this and so the content would differ. (since
|
||||
// the .html file in question adds content to the page when print events
|
||||
// happen)
|
||||
await waitForFileToAlmostMatchSize(
|
||||
filePath,
|
||||
mockCA.calls[0].printDataSize
|
||||
);
|
||||
|
||||
await IOUtils.remove(filePath);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function testPrintToStreamWithContentAnalysisActiveAndBlocking() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(false);
|
||||
|
||||
try {
|
||||
await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
ok(false, "Content analysis should make this fail to print");
|
||||
} catch (e) {
|
||||
ok(
|
||||
/NS_ERROR_CONTENT_BLOCKED/.test(e.toString()),
|
||||
"Got content blocked error"
|
||||
);
|
||||
}
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function testPrintToStreamWithContentAnalysisReturningError() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
expectUncaughtException();
|
||||
mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
try {
|
||||
await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
ok(false, "Content analysis should make this fail to print");
|
||||
} catch (e) {
|
||||
ok(
|
||||
/NS_ERROR_NOT_AVAILABLE/.test(e.toString()),
|
||||
"Error in mock CA was propagated out"
|
||||
);
|
||||
}
|
||||
is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function testPrintThroughDialogWithContentAnalysisActive() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(true);
|
||||
|
||||
let fileName = addUniqueSuffix(`printDialogTest`);
|
||||
let file = helper.mockFilePicker(fileName);
|
||||
info(`Printing to ${file.path}`);
|
||||
await helper.startPrint();
|
||||
await helper.assertPrintToFile(file, () => {
|
||||
EventUtils.sendKey("return", helper.win);
|
||||
});
|
||||
|
||||
is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
|
||||
await waitForFileToAlmostMatchSize(
|
||||
file.path,
|
||||
mockCA.calls[0].printDataSize
|
||||
);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(false);
|
||||
|
||||
await helper.startPrint();
|
||||
let fileName = addUniqueSuffix(`printDialogTest`);
|
||||
let file = helper.mockFilePicker(fileName);
|
||||
info(`Printing to ${file.path}`);
|
||||
try {
|
||||
await helper.assertPrintToFile(file, () => {
|
||||
EventUtils.sendKey("return", helper.win);
|
||||
});
|
||||
} catch (e) {
|
||||
ok(
|
||||
/Wait for target file to get created/.test(e.toString()),
|
||||
"Target file should not get created"
|
||||
);
|
||||
}
|
||||
ok(!file.exists(), "File should not exist");
|
||||
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function testPrintThroughDialogWithContentAnalysisReturningError() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
expectUncaughtException();
|
||||
mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
await helper.startPrint();
|
||||
let fileName = addUniqueSuffix(`printDialogTest`);
|
||||
let file = helper.mockFilePicker(fileName);
|
||||
info(`Printing to ${file.path}`);
|
||||
try {
|
||||
await helper.assertPrintToFile(file, () => {
|
||||
EventUtils.sendKey("return", helper.win);
|
||||
});
|
||||
} catch (e) {
|
||||
ok(
|
||||
/Wait for target file to get created/.test(e.toString()),
|
||||
"Target file should not get created"
|
||||
);
|
||||
}
|
||||
ok(!file.exists(), "File should not exist");
|
||||
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
@ -0,0 +1,390 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Services.scriptloader.loadSubScript(
|
||||
"chrome://mochitests/content/browser/toolkit/components/printing/tests/head.js",
|
||||
this
|
||||
);
|
||||
|
||||
const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
|
||||
Ci.nsIPrintSettingsService
|
||||
);
|
||||
|
||||
let mockCA = {
|
||||
isActive: true,
|
||||
mightBeActive: true,
|
||||
errorValue: undefined,
|
||||
|
||||
setupForTest(shouldAllowRequest) {
|
||||
this.shouldAllowRequest = shouldAllowRequest;
|
||||
this.errorValue = undefined;
|
||||
this.calls = [];
|
||||
},
|
||||
|
||||
setupForTestWithError(errorValue) {
|
||||
this.errorValue = errorValue;
|
||||
this.calls = [];
|
||||
},
|
||||
|
||||
clearCalls() {
|
||||
this.calls = [];
|
||||
},
|
||||
|
||||
getAction() {
|
||||
if (this.shouldAllowRequest === undefined) {
|
||||
this.shouldAllowRequest = true;
|
||||
}
|
||||
return this.shouldAllowRequest
|
||||
? Ci.nsIContentAnalysisResponse.eAllow
|
||||
: Ci.nsIContentAnalysisResponse.eBlock;
|
||||
},
|
||||
|
||||
// nsIContentAnalysis methods
|
||||
async analyzeContentRequest(request, _autoAcknowledge) {
|
||||
info(
|
||||
"Mock ContentAnalysis service: analyzeContentRequest, this.shouldAllowRequest=" +
|
||||
this.shouldAllowRequest +
|
||||
", this.errorValue=" +
|
||||
this.errorValue
|
||||
);
|
||||
this.calls.push(request);
|
||||
if (this.errorValue) {
|
||||
throw this.errorValue;
|
||||
}
|
||||
// Use setTimeout to simulate an async activity
|
||||
await new Promise(res => setTimeout(res, 0));
|
||||
return makeContentAnalysisResponse(this.getAction(), request.requestToken);
|
||||
},
|
||||
|
||||
analyzeContentRequestCallback(request, autoAcknowledge, callback) {
|
||||
info(
|
||||
"Mock ContentAnalysis service: analyzeContentRequestCallback, this.shouldAllowRequest=" +
|
||||
this.shouldAllowRequest +
|
||||
", this.errorValue=" +
|
||||
this.errorValue
|
||||
);
|
||||
this.calls.push(request);
|
||||
if (this.errorValue) {
|
||||
throw this.errorValue;
|
||||
}
|
||||
let response = makeContentAnalysisResponse(
|
||||
this.getAction(),
|
||||
request.requestToken
|
||||
);
|
||||
// Use setTimeout to simulate an async activity
|
||||
setTimeout(() => {
|
||||
callback.contentResult(response);
|
||||
}, 0);
|
||||
},
|
||||
};
|
||||
|
||||
add_setup(async function test_setup() {
|
||||
mockCA = mockContentAnalysisService(mockCA);
|
||||
});
|
||||
|
||||
const TEST_PAGE_URL =
|
||||
"https://example.com/browser/toolkit/components/printing/tests/simplifyArticleSample.html";
|
||||
const TEST_PAGE_URL_2 =
|
||||
"https://example.com/browser/toolkit/components/printing/tests/longerArticle.html";
|
||||
|
||||
function addUniqueSuffix(prefix) {
|
||||
return `${prefix}-${Services.uuid
|
||||
.generateUUID()
|
||||
.toString()
|
||||
.slice(1, -1)}.pdf`;
|
||||
}
|
||||
|
||||
async function printToDestination(aBrowser, aDestination) {
|
||||
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
let fileName = addUniqueSuffix(`printDestinationTest-${aDestination}`);
|
||||
let filePath = PathUtils.join(tmpDir.path, fileName);
|
||||
|
||||
info(`Printing to ${filePath}`);
|
||||
|
||||
let settings = PSSVC.createNewPrintSettings();
|
||||
settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
|
||||
settings.outputDestination = aDestination;
|
||||
|
||||
settings.headerStrCenter = "";
|
||||
settings.headerStrLeft = "";
|
||||
settings.headerStrRight = "";
|
||||
settings.footerStrCenter = "";
|
||||
settings.footerStrLeft = "";
|
||||
settings.footerStrRight = "";
|
||||
|
||||
settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */
|
||||
let outStream = null;
|
||||
if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) {
|
||||
settings.toFileName = PathUtils.join(tmpDir.path, fileName);
|
||||
} else {
|
||||
is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream);
|
||||
outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
|
||||
Ci.nsIFileOutputStream
|
||||
);
|
||||
let tmpFile = tmpDir.clone();
|
||||
tmpFile.append(fileName);
|
||||
outStream.init(tmpFile, -1, 0o666, 0);
|
||||
settings.outputStream = outStream;
|
||||
}
|
||||
|
||||
await aBrowser.browsingContext.print(settings);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function assertContentAnalysisRequest(request, expectedUrl) {
|
||||
is(request.url.spec, expectedUrl ?? TEST_PAGE_URL, "request has correct URL");
|
||||
is(
|
||||
request.analysisType,
|
||||
Ci.nsIContentAnalysisRequest.ePrint,
|
||||
"request has print analysisType"
|
||||
);
|
||||
is(
|
||||
request.operationTypeForDisplay,
|
||||
Ci.nsIContentAnalysisRequest.eOperationPrint,
|
||||
"request has print operationTypeForDisplay"
|
||||
);
|
||||
is(request.textContent, "", "request textContent should be empty");
|
||||
is(request.filePath, "", "request filePath should be empty");
|
||||
isnot(request.printDataHandle, 0, "request printDataHandle should not be 0");
|
||||
isnot(request.printDataSize, 0, "request printDataSize should not be 0");
|
||||
ok(!!request.requestToken.length, "request requestToken should not be empty");
|
||||
}
|
||||
|
||||
// Printing to a stream is different than going through the print preview dialog because it
|
||||
// doesn't make a static clone of the document before the print, which causes the
|
||||
// Content Analysis code to go through a different code path. This is similar to what
|
||||
// happens when various preferences are set to skip the print preview dialog, for example
|
||||
// print.prefer_system_dialog.
|
||||
add_task(
|
||||
async function testPrintToStreamWithContentAnalysisActiveAndAllowing() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(true);
|
||||
|
||||
let filePath = await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
|
||||
await waitForFileToAlmostMatchSize(
|
||||
filePath,
|
||||
mockCA.calls[0].printDataSize
|
||||
);
|
||||
|
||||
await IOUtils.remove(filePath);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function testPrintToStreamAfterNavigationWithContentAnalysisActiveAndAllowing() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(true);
|
||||
|
||||
let filePath = await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
mockCA.clearCalls();
|
||||
|
||||
await IOUtils.remove(filePath);
|
||||
|
||||
BrowserTestUtils.startLoadingURIString(
|
||||
helper.sourceBrowser,
|
||||
TEST_PAGE_URL_2
|
||||
);
|
||||
await BrowserTestUtils.browserLoaded(helper.sourceBrowser);
|
||||
|
||||
filePath = await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0], TEST_PAGE_URL_2);
|
||||
await waitForFileToAlmostMatchSize(
|
||||
filePath,
|
||||
mockCA.calls[0].printDataSize
|
||||
);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function testPrintToStreamWithContentAnalysisActiveAndBlocking() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(false);
|
||||
|
||||
try {
|
||||
await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
ok(false, "Content analysis should make this fail to print");
|
||||
} catch (e) {
|
||||
ok(
|
||||
/NS_ERROR_CONTENT_BLOCKED/.test(e.toString()),
|
||||
"Got content blocked error"
|
||||
);
|
||||
}
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function testPrintToStreamWithContentAnalysisReturningError() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
expectUncaughtException();
|
||||
mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
try {
|
||||
await printToDestination(
|
||||
helper.sourceBrowser,
|
||||
Ci.nsIPrintSettings.kOutputDestinationFile
|
||||
);
|
||||
ok(false, "Content analysis should make this fail to print");
|
||||
} catch (e) {
|
||||
ok(
|
||||
/NS_ERROR_NOT_AVAILABLE/.test(e.toString()),
|
||||
"Error in mock CA was propagated out"
|
||||
);
|
||||
}
|
||||
is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function testPrintThroughDialogWithContentAnalysisActive() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(true);
|
||||
|
||||
await helper.startPrint();
|
||||
let fileName = addUniqueSuffix(`printDialogTest`);
|
||||
let file = helper.mockFilePicker(fileName);
|
||||
info(`Printing to ${file.path}`);
|
||||
await helper.assertPrintToFile(file, () => {
|
||||
EventUtils.sendKey("return", helper.win);
|
||||
});
|
||||
|
||||
is(mockCA.calls.length, 1, "Correct number of calls to Content Analysis");
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
|
||||
await waitForFileToAlmostMatchSize(
|
||||
file.path,
|
||||
mockCA.calls[0].printDataSize
|
||||
);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function testPrintThroughDialogWithContentAnalysisActiveAndBlocking() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
mockCA.setupForTest(false);
|
||||
|
||||
await helper.startPrint();
|
||||
let fileName = addUniqueSuffix(`printDialogTest`);
|
||||
let file = helper.mockFilePicker(fileName);
|
||||
info(`Printing to ${file.path}`);
|
||||
try {
|
||||
await helper.assertPrintToFile(file, () => {
|
||||
EventUtils.sendKey("return", helper.win);
|
||||
});
|
||||
} catch (e) {
|
||||
ok(
|
||||
/Wait for target file to get created/.test(e.toString()),
|
||||
"Target file should not get created"
|
||||
);
|
||||
}
|
||||
ok(!file.exists(), "File should not exist");
|
||||
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function testPrintThroughDialogWithContentAnalysisReturningError() {
|
||||
await PrintHelper.withTestPage(
|
||||
async helper => {
|
||||
expectUncaughtException();
|
||||
mockCA.setupForTestWithError(Cr.NS_ERROR_NOT_AVAILABLE);
|
||||
|
||||
await helper.startPrint();
|
||||
let fileName = addUniqueSuffix(`printDialogTest`);
|
||||
let file = helper.mockFilePicker(fileName);
|
||||
info(`Printing to ${file.path}`);
|
||||
try {
|
||||
await helper.assertPrintToFile(file, () => {
|
||||
EventUtils.sendKey("return", helper.win);
|
||||
});
|
||||
} catch (e) {
|
||||
ok(
|
||||
/Wait for target file to get created/.test(e.toString()),
|
||||
"Target file should not get created"
|
||||
);
|
||||
}
|
||||
ok(!file.exists(), "File should not exist");
|
||||
|
||||
is(
|
||||
mockCA.calls.length,
|
||||
1,
|
||||
"Correct number of calls to Content Analysis"
|
||||
);
|
||||
assertContentAnalysisRequest(mockCA.calls[0]);
|
||||
},
|
||||
TEST_PAGE_URL,
|
||||
true
|
||||
);
|
||||
}
|
||||
);
|
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<p>Some random text</p>
|
||||
<button onclick="print()">Print the page</button>
|
||||
<pre id="log"></pre>
|
||||
<script>
|
||||
let i = 0;
|
||||
for (let t of ["beforeprint", "afterprint"]) {
|
||||
addEventListener(t, () => {
|
||||
document.getElementById("log").appendChild(document.createTextNode(`[${i++}] ${t}\n`));
|
||||
});
|
||||
}
|
||||
</script>
|
114
toolkit/components/contentanalysis/tests/browser/head.js
Normal file
114
toolkit/components/contentanalysis/tests/browser/head.js
Normal file
@ -0,0 +1,114 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { MockRegistrar } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/MockRegistrar.sys.mjs"
|
||||
);
|
||||
|
||||
// Wraps the given object in an XPConnect wrapper and, if an interface
|
||||
// is passed, queries the result to that interface.
|
||||
function xpcWrap(obj, iface) {
|
||||
let ifacePointer = Cc[
|
||||
"@mozilla.org/supports-interface-pointer;1"
|
||||
].createInstance(Ci.nsISupportsInterfacePointer);
|
||||
|
||||
ifacePointer.data = obj;
|
||||
if (iface) {
|
||||
return ifacePointer.data.QueryInterface(iface);
|
||||
}
|
||||
return ifacePointer.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock a (set of) service(s) as the object mockService.
|
||||
*
|
||||
* @param {[string]} serviceNames
|
||||
* array of services names that mockService will be
|
||||
* allowed to QI to. Must include the name of the
|
||||
* service referenced by contractId.
|
||||
* @param {string} contractId
|
||||
* the component ID that will reference the mock object
|
||||
* instead of the original service
|
||||
* @param {object} interfaceObj
|
||||
* interface object for the component
|
||||
* @param {object} mockService
|
||||
* object that satisfies the contract well
|
||||
* enough to use as a mock of it
|
||||
* @returns {object} The newly-mocked service
|
||||
*/
|
||||
function mockService(serviceNames, contractId, interfaceObj, mockService) {
|
||||
// xpcWrap allows us to mock [implicit_jscontext] methods.
|
||||
let newService = {
|
||||
...mockService,
|
||||
QueryInterface: ChromeUtils.generateQI(serviceNames),
|
||||
};
|
||||
let o = xpcWrap(newService, interfaceObj);
|
||||
let cid = MockRegistrar.register(contractId, o);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(cid);
|
||||
});
|
||||
return newService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock the nsIContentAnalysis service with the object mockCAService.
|
||||
*
|
||||
* @param {object} mockCAService
|
||||
* the service to mock for nsIContentAnalysis
|
||||
* @returns {object} The newly-mocked service
|
||||
*/
|
||||
function mockContentAnalysisService(mockCAService) {
|
||||
return mockService(
|
||||
["nsIContentAnalysis"],
|
||||
"@mozilla.org/contentanalysis;1",
|
||||
Ci.nsIContentAnalysis,
|
||||
mockCAService
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an nsIContentAnalysisResponse.
|
||||
*
|
||||
* @param {number} action The action to take, from the
|
||||
* nsIContentAnalysisResponse.Action enum.
|
||||
* @param {string} token The requestToken.
|
||||
* @returns {object} An object that conforms to nsIContentAnalysisResponse.
|
||||
*/
|
||||
function makeContentAnalysisResponse(action, token) {
|
||||
return {
|
||||
action,
|
||||
shouldAllowContent: action != Ci.nsIContentAnalysisResponse.eBlock,
|
||||
requestToken: token,
|
||||
acknowledge: _acknowledgement => {},
|
||||
};
|
||||
}
|
||||
|
||||
async function waitForFileToAlmostMatchSize(filePath, expectedSize) {
|
||||
// In Cocoa the CGContext adds a hash, plus there are other minor
|
||||
// non-user-visible differences, so we need to be a bit more sloppy there.
|
||||
//
|
||||
// We see one byte difference in Windows and Linux on automation sometimes,
|
||||
// though files are consistently the same locally, that needs
|
||||
// investigation, but it's probably harmless.
|
||||
// Note that this is copied from browser_print_stream.js.
|
||||
const maxSizeDifference = AppConstants.platform == "macosx" ? 100 : 3;
|
||||
|
||||
// Buffering shenanigans? Wait for sizes to match... There's no great
|
||||
// IOUtils methods to force a flush without writing anything...
|
||||
// Note that this means if this results in a timeout this is exactly
|
||||
// the same as a test failure.
|
||||
// This is taken from toolkit/components/printing/tests/browser_print_stream.js
|
||||
await TestUtils.waitForCondition(async function () {
|
||||
let fileStat = await IOUtils.stat(filePath);
|
||||
|
||||
info("got size: " + fileStat.size + " expected: " + expectedSize);
|
||||
Assert.greater(
|
||||
fileStat.size,
|
||||
0,
|
||||
"File should not be empty: " + fileStat.size
|
||||
);
|
||||
return Math.abs(fileStat.size - expectedSize) <= maxSizeDifference;
|
||||
}, "Sizes should (almost) match");
|
||||
}
|
@ -60,6 +60,9 @@ class PrintHelper {
|
||||
}
|
||||
|
||||
static getTestPageUrl(pathName) {
|
||||
if (pathName.startsWith("http://")) {
|
||||
return pathName;
|
||||
}
|
||||
const testPath = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"http://example.com"
|
||||
@ -68,6 +71,9 @@ class PrintHelper {
|
||||
}
|
||||
|
||||
static getTestPageUrlHTTPS(pathName) {
|
||||
if (pathName.startsWith("https://")) {
|
||||
return pathName;
|
||||
}
|
||||
const testPath = getRootDirectory(gTestPath).replace(
|
||||
"chrome://mochitests/content",
|
||||
"https://example.com"
|
||||
|
@ -20,8 +20,12 @@ contentanalysis-slow-agent-dialog-body-clipboard = { $agent } is reviewing what
|
||||
# Variables:
|
||||
# $agent - The name of the DLP agent doing the analysis
|
||||
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.
|
||||
# Variables:
|
||||
# $agent - The name of the DLP agent doing the analysis
|
||||
contentanalysis-slow-agent-dialog-body-print = { $agent } is reviewing what you printed against your organization’s data policies. This may take a moment.
|
||||
contentanalysis-operationtype-clipboard = clipboard
|
||||
contentanalysis-operationtype-dropped-text = dropped text
|
||||
contentanalysis-operationtype-print = print
|
||||
# $filename - The filename associated with the request, such as "aFile.txt"
|
||||
contentanalysis-customdisplaystring-description = upload of “{ $filename }”
|
||||
|
||||
|
@ -335,7 +335,7 @@ interface nsIPrintSettings : nsISupports
|
||||
*/
|
||||
attribute AString toFileName;
|
||||
|
||||
attribute nsIOutputStream outputStream; /* for kOutputDestinationPrinter */
|
||||
attribute nsIOutputStream outputStream; /* for kOutputDestinationStream */
|
||||
|
||||
[infallible] attribute long printPageDelay; /* in milliseconds */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user