flypig.co.uk

List items

Items from the current list are shown below.

Blog

11 Dec 2023 : Day 104 #
We're back on printing again today. Yesterday we tracked down the nsGlobalWindowOuter::Print() method which appears to be responsible for cloning the document ready for printing. That, in turn, appears to be called by BrowserChild::RecvPrint(). And this method deserves some explanation.

We've discussed the gecko IPC mechanism in previous posts, in fact way back on Day 7. If you read those then... well, first, kudos for keeping up! But second you'll recall there are ipdl files which define an interface and that the build process generates parent and child interfaces from these that allow message passing from one to the other.

Whenever we see a SendName() or RecvName() method like this RecvPrint() method we have here, it's a good sign that this is related to this message passing. The Send method is called on one thread and the message, which is a bit like a remote method call, triggers the Recv method to be called on a (potentially different) thread. That's my rather basic understanding of it, at any rate.

The message, along with the sending and receiving mechanisms, are generated by the build process in the form of a file that has a P at the start of the name. I'm not exactly sure what the P stands for. The sender tends to be called the parent actor, and the receiver the child actor. That's why we're seeing RecvPrint() in the BrowserChild class.

If we look at the class definition for BrowserChild in the header file it looks like this:
/**
 * BrowserChild implements the child actor part of the PBrowser protocol. See
 * PBrowser for more information.
 */
class BrowserChild final : public nsMessageManagerScriptExecutor,
                           public ipc::MessageManagerCallback,
                           public PBrowserChild,
                           public nsIWebBrowserChrome,
                           public nsIEmbeddingSiteWindow,
                           public nsIWebBrowserChromeFocus,
                           public nsIInterfaceRequestor,
                           public nsIWindowProvider,
                           public nsSupportsWeakReference,
                           public nsIBrowserChild,
                           public nsIObserver,
                           public nsIWebProgressListener2,
                           public TabContext,
                           public nsITooltipListener,
                           public mozilla::ipc::IShmemAllocator {
[...]
Notice how it inherits from the PBrowserChild class. That's the child actor class interface that's autogenerated from the PBrowser.ipdl file. If we look in the PBrowser.ipdl file we can see the definition of the Print() method we're interested in:
    /**
     * Tell the child to print the current page with the given settings.
     *
     * @param aBrowsingContext the browsing context to print.
     * @param aPrintData the serialized settings to print with
     */
    async Print(MaybeDiscardedBrowsingContext aBC, PrintData aPrintData);
That's not quite the end of it though, because — to round things off — there's also a PBrowserParent class that's been generated from the IPDL file as well. Like the child class it has both a header and a source file. In the source file we can find the definition for the SendPrint() method like this:
auto PBrowserParent::SendPrint(
        const MaybeDiscardedBrowsingContext& aBC,
        const PrintData& aPrintData) -> bool
{
All of this is inherited by the BrowserParent class in the BrowserParent.h file like this:
/**
 * BrowserParent implements the parent actor part of the PBrowser protocol. See
 * PBrowser for more information.
 */
class BrowserParent final : public PBrowserParent,
                            public nsIDOMEventListener,
                            public nsIAuthPromptProvider,
                            public nsSupportsWeakReference,
                            public TabContext,
                            public LiveResizeListener {
[...]
This class doesn't override the SendPrint() method but it does inherit it. So there's quite a structure and class hierarchy that's built up from these IPDL files.

The key takeaway for what we're trying to achieve is that if we want to trigger the nsGlobalWindowOuter::Print() method, we're going to need to call the BrowserParent::SendPrint() method from somewhere. Checking through the code it's clear that nothing is inheriting from BrowserParent but there are plenty of places which give access to the BrowserParent interface.

For example the BrowserBridgeParent class has this method:
  BrowserParent* GetBrowserParent() { return mBrowserParent; }
There are quite a few other similar method scattered around the place and I honestly don't know which I'm supposed to end up using.
$ grep -rIn "BrowserParent\* Get" * --include="*.h"
layout/base/PresShell.h:181:
    static dom::BrowserParent* GetCapturingRemoteTarget() {
docshell/base/CanonicalBrowsingContext.h:222:
    BrowserParent* GetBrowserParent() const;
dom/base/nsFrameLoader.h:338:
    BrowserParent* GetBrowserParent() const;
dom/base/PointerLockManager.h:38:
    static dom::BrowserParent* GetLockedRemoteTarget();
dom/base/nsContentUtils.h:501:
    static mozilla::dom::BrowserParent* GetCommonBrowserParentAncestor(
dom/ipc/BrowserHost.h:54:
    BrowserParent* GetActor() { return mRoot; }
dom/ipc/WindowGlobalParent.h:105:
    BrowserParent* GetBrowserParent();
dom/ipc/BrowserParent.h:114:
    static BrowserParent* GetFocused();
dom/ipc/BrowserParent.h:116:
    static BrowserParent* GetLastMouseRemoteTarget();
dom/ipc/BrowserParent.h:118:
    static BrowserParent* GetFrom(nsFrameLoader* aFrameLoader);
dom/ipc/BrowserParent.h:120:
    static BrowserParent* GetFrom(PBrowserParent* aBrowserParent);
dom/ipc/BrowserParent.h:122:
    static BrowserParent* GetFrom(nsIContent* aContent);
dom/ipc/BrowserParent.h:124:
    static BrowserParent* GetBrowserParentFromLayersId(
dom/ipc/BrowserBridgeParent.h:43:
    BrowserParent* GetBrowserParent() { return mBrowserParent; }
dom/events/TextComposition.h:82:
    BrowserParent* GetBrowserParent() const { return mBrowserParent; }
dom/events/IMEStateManager.h:55:
    static BrowserParent* GetActiveBrowserParent() {
dom/events/PointerEventHandler.h:95:
    static dom::BrowserParent* GetPointerCapturingRemoteTarget(
dom/events/EventStateManager.h:1050:
    dom::BrowserParent* GetCrossProcessTarget();
Hopefully this will all become clear in due course.

I'm also interested to discover that the CanonicalBrowsingContext class has a Print() method that calls SendPrint():
already_AddRefed<Promise> CanonicalBrowsingContext::Print(
    nsIPrintSettings* aPrintSettings, ErrorResult& aRv) {
  RefPtr<Promise> promise = Promise::Create(GetIncumbentGlobal(), aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return promise.forget();
  }
[...]

  auto* browserParent = GetBrowserParent();
  if (NS_WARN_IF(!browserParent)) {
    promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE));
    return promise.forget();
  }

  RefPtr<embedding::PrintingParent> printingParent =
      browserParent->Manager()->GetPrintingParent();

  embedding::PrintData printData;
  nsresult rv = printingParent->SerializeAndEnsureRemotePrintJob(
      aPrintSettings, listener, nullptr, &printData);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    promise->MaybeReject(ErrorResult(rv));
    return promise.forget();
  }

  if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
    promise->MaybeReject(ErrorResult(NS_ERROR_FAILURE));
  }
  return promise.forget();
#endif
}
Maybe we're going to either have to call this or do something similar. Let's head back to the code we're already using to do the printing. Recall that this lives in DownloadCore.jsm in the form of our newly added DownloadPDFSaver class.

Now that I'm comparing against the two there are some aspects that I think it's worth taking note of. The main input to the nsIWebBrowserPrint::Print() method that we're currently calling in DownloadPDFSaver takes in an object that implements the nsIPrintSettings interface and returns a promise. From the code I listed above for CanonicalBrowsingContext::Print() you'll notice that this also takes in an object that implements the nsIPrintSettings interface and returns a promise.

So calling switching to call the latter may require only minimal changes. The question then is where to get the CanonicalBrowsingContext from. The method we're currently using is hanging off of a windowRef:
    let win = this.download.source.windowRef.get();
[...]
    this._webBrowserPrint = win.getInterface(Ci.nsIWebBrowserPrint);
Because it's JavaScript from this there's absolutely no indication of what type win is of course. I'm going to need to know in order to make progress.

Digging back through the source in DownloadCore.jsm I can see that windowRef gets set to something that implements Ci.nsIDOMWindow:
DownloadSource.fromSerializable = function(aSerializable) {
[...]
  } else if (aSerializable instanceof Ci.nsIDOMWindow) {
    source.url = aSerializable.location.href;
    source.isPrivate = PrivateBrowsingUtils.isContentWindowPrivate(
      aSerializable
    );
    source.windowRef = Cu.getWeakReference(aSerializable);
[...]
I've spent a lot of time analysing the code today without actually making any changes at all to the code itself. I'm keen to actually try some things out in the JavaScript to see whether I can extract something useful from the windowRef that will allow us to call the SendPrint() method that we're so interested in. But that will have to wait for tomorrow now.

If you'd like to read any of my other gecko diary entries, they're all available on my Gecko-dev Diary page.

Comments

Uncover Disqus comments