flypig.co.uk

List items

Items from the current list are shown below.

Blog

15 Dec 2023 : Day 108 #
This morning: a successful build. That's not really so surprising given the minimal changes I made yesterday, but I've messed up smaller pieces of code before, so you can never be sure.

So, packages built, installed and run, and what do we have? In order to get the debug output I have to set the MOZ_LOG environment variable to include "EmbedLite:5" so that the LOGE() messages will show. Here's what happens when I press the "Save web page as PDF" option:
$ EMBED_CONSOLE=1 MOZ_LOG="EmbedLite:5" sailfish-browser
[...]
[Parent 15259: Unnamed thread 7060002670]: E/EmbedLite FUNC::virtual nsresult 
    mozilla::embedlite::EmbedLiteAppChild::Observe(nsISupports*, const char*,
    const char16_t*):68 topic:embed:download
[Parent 15259: Unnamed thread 7060002670]: W/EmbedLite ERROR:
    EmbedLite::virtual nsresult WindowCreator::CreateChromeWindow
    (nsIWebBrowserChrome*, uint32_t, nsIOpenWindowInfo*, bool*,
    nsIWebBrowserChrome**):61 PRINT: isForPrinting: 1
EmbedliteDownloadManager error: [Exception... "The request is not allowed."
    nsresult: "0x80530021 (NS_ERROR_DOM_NOT_ALLOWED_ERR)"  location:
    "JS frame :: resource://gre/modules/DownloadCore.jsm ::
    DownloadError :: line 1755"  data: no]
[Parent 15259: Unnamed thread 7060002670]: E/EmbedLite FUNC::virtual nsresult mozilla::embedlite::EmbedLiteAppChild::Observe(nsISupports*, const char*,
    const char16_t*):68 topic:embed:download
JavaScript error: , line 0: uncaught exception: Object
CONSOLE message:
[JavaScript Error: "uncaught exception: Object"]
That's a bit messy, but if you look through carefully it's possible to see the output PRINT: isForPrinting: 1. That's a good sign: it shows that in WindowCreator::CreateChromeWindow() we can find out whether this is a window that needs to be hidden or not.

But the rest is less encouraging. Apart from the logging I also added an early return to the method to prevent the window from actually being created. I got the method to return NS_OK in the hope that whatever happens further down the stack might not notice. But from this debug output we can see that it did notice, throwing an exception "The request is not allowed."

From DOMException.h we can see that this NS_ERROR_DOM_NOT_ALLOWED_ERR that we're getting is equivalent to NotAllowedError which appears one in DownloadCore.jsm. However in this particular instance it's just some code conditioned on the error. What we need is some code that's actually generating the error. Looking through the rest of the code, it all looks a bit peculiar: this error is usually triggered by a an authentication failure, which doesn't fit with what we're doing here at all.

There are only a few places where it seems to be used for other purposes. One of them is the StyleSheet::InsertRuleIntoGroup() method where it seems to be caused by a failed attempt to modify a group.
nsresult StyleSheet::InsertRuleIntoGroup(const nsACString& aRule,
                                         css::GroupRule* aGroup,
                                         uint32_t aIndex) {
  NS_ASSERTION(IsComplete(), "No inserting into an incomplete sheet!");
  // check that the group actually belongs to this sheet!
  if (this != aGroup->GetStyleSheet()) {
    return NS_ERROR_INVALID_ARG;
  }

  if (IsReadOnly()) {
    return NS_OK;
  }

  if (ModificationDisallowed()) {
    return NS_ERROR_DOM_NOT_ALLOWED_ERR;
  }
[...]
I'm running sailfish-browser through the debugger with a break point on this method to see whether this is where it's coming from. If it is, I'm not sure what that will tell us.

But the breakpoint isn't triggered, so it's not this bit of code anyway. After grepping the code a bit more and sifting carefully through various files, I eventually realise that there's an instance of this error that could potentially be generated directly after our call to OpenInternal() in nsGlobalWindowOuter.cpp:
[...]
      aError = OpenInternal(u""_ns, u""_ns, u""_ns,
                            false,             // aDialog
                            false,             // aContentModal
                            true,              // aCalledNoScript
                            false,             // aDoJSFixups
                            true,              // aNavigate
                            nullptr, nullptr,  // No args
                            nullptr,           // aLoadState
                            false,             // aForceNoOpener
                            printKind, getter_AddRefs(bc));
      if (NS_WARN_IF(aError.Failed())) {
        return nullptr;
      }
    }
    if (!bc) {
      aError.ThrowNotAllowedError("No browsing context");
      return nullptr;
    }
That looks like a far more promising case. The debugger won't let me put a breakpoint directly on the line that's throwing the exception here, but it will let me put one on the OpenInternal() call, so I can set that and step through to check whether this error is the one causing the output.
(gdb) break nsGlobalWindowOuter.cpp:5329
Breakpoint 4 at 0x7fba96fad0: file dom/base/nsGlobalWindowOuter.cpp, line 5329.
(gdb) c
Continuing.
[LWP 17314 exited]
[Parent 16702: Unnamed thread 7f88002670]: E/EmbedLite FUNC::virtual nsresult mozilla::embedlite::EmbedLiteAppChild::Observe(nsISupports*, const char*,
    const char16_t*):68 topic:embed:download
[Switching to LWP 16938]

Thread 8 "GeckoWorkerThre" hit Breakpoint 4, nsGlobalWindowOuter::Print
    (nsIPrintSettings*, nsIWebProgressListener*, nsIDocShell*,
    nsGlobalWindowOuter::IsPreview, nsGlobalWindowOuter::IsForWindowDotPrint,
    std::function<void (mozilla::dom::PrintPreviewResultInfo const&)>&&,
    mozilla::ErrorResult&) (this=this@entry=0x7f88564870,
    aPrintSettings=aPrintSettings@entry=0x7e352ed060,
    aListener=aListener@entry=0x7f89bfa6b0, 
    aDocShellToCloneInto=aDocShellToCloneInto@entry=0x0,
    aIsPreview=aIsPreview@entry=nsGlobalWindowOuter::IsPreview::No, 
    aForWindowDotPrint=aForWindowDotPrint@entry=nsGlobalWindowOuter::
    IsForWindowDotPrint::No, aPrintPreviewCallback=..., aError=...)
    at dom/base/nsGlobalWindowOuter.cpp:5329
5329          aError = OpenInternal(u""_ns, u""_ns, u""_ns,
(gdb) n
[Parent 16702: Unnamed thread 7f88002670]: W/EmbedLite ERROR:
    EmbedLite::virtual nsresult WindowCreator::CreateChromeWindow
    (nsIWebBrowserChrome*, uint32_t, nsIOpenWindowInfo*, bool*,
    nsIWebBrowserChrome**):61 PRINT: isForPrinting: 1
5329          aError = OpenInternal(u""_ns, u""_ns, u""_ns,
(gdb) n
30      ${PROJECT}/obj-build-mer-qt-xr/dist/include/nsError.h:
    No such file or directory.
(gdb) n
5343        if (!bc) {
(gdb) p bc
$1 = {mRawPtr = 0x0}
(gdb) n
5344          aError.ThrowNotAllowedError("No browsing context");
(gdb) n
5345          return nullptr;
(gdb) 
So that's the one. It's also clear from this why the error is happening: by not creating the window we're obviously also causing the creation of the browser context bc to fail.

The obvious follow-up question is why the lack of window is preventing the browsing context from getting created. Well have to follow the metaphorical rabbit down the rabbit hole to find out. So the context is returned as the last parameter of the OpenInternal() call:
nsresult nsGlobalWindowOuter::OpenInternal(
    const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
    bool aDialog, bool aContentModal, bool aCalledNoScript, bool aDoJSFixups,
    bool aNavigate, nsIArray* argv, nsISupports* aExtraArgument,
    nsDocShellLoadState* aLoadState, bool aForceNoOpener, PrintKind aPrintKind,
    BrowsingContext** aReturn)
In practice it's the domReturn variable inside this method that interests us. This is set as the last parameter of OpenWindow2() called inside this method:
      rv = pwwatch->OpenWindow2(this, url, name, options,
                                /* aCalledFromScript = */ true, aDialog,
                                aNavigate, argv, isPopupSpamWindow,
                                forceNoOpener, forceNoReferrer, wwPrintKind,
                                aLoadState, getter_AddRefs(domReturn));
And then this comes back from the call to OpenWindowInternal() that's being called from inside this method, again as the last parameter:
  return OpenWindowInternal(aParent, aUrl, aName, aFeatures, aCalledFromScript,
                            dialog, aNavigate, argv, aIsPopupSpam,
                            aForceNoOpener, aForceNoReferrer, aPrintKind,
                            aLoadState, aResult);
In this method the variable we're interested in is newBC which ends up turning in to the returned browser context value. Now this doesn't get directly returned by the next level. Instead there's some code that looks like this:
      /* We can give the window creator some hints. The only hint at this time
         is whether the opening window is in a situation that's likely to mean
         this is an unrequested popup window we're creating. However we're not
         completely honest: we clear that indicator if the opener is chrome, so
         that the downstream consumer can treat the indicator to mean simply
         that the new window is subject to popup control. */
      rv = CreateChromeWindow(parentChrome, chromeFlags, openWindowInfo,
                              getter_AddRefs(newChrome));
      if (parentTopInnerWindow) {
        parentTopInnerWindow->Resume();
      }

      if (newChrome) {
        /* It might be a chrome AppWindow, in which case it won't have
            an nsIDOMWindow (primary content shell). But in that case, it'll
            be able to hand over an nsIDocShellTreeItem directly. */
        nsCOMPtr<nsPIDOMWindowOuter> newWindow(do_GetInterface(newChrome));
        nsCOMPtr<nsIDocShellTreeItem> newDocShellItem;
        if (newWindow) {
          newDocShellItem = newWindow->GetDocShell();
        }
        if (!newDocShellItem) {
          newDocShellItem = do_GetInterface(newChrome);
        }
        if (!newDocShellItem) {
          rv = NS_ERROR_FAILURE;
        }
        newBC = newDocShellItem->GetBrowsingContext();
      }
Looking at this code, the most likely explanation for newBC being null is that newChrome is being returned as null from the CreateChromeWindow() call. So let's follow this lead into CreateChromeWindow(). Now we're interested in the newWindowChrome variable and we have some code that looks like this:
  bool cancel = false;
  nsCOMPtr<nsIWebBrowserChrome> newWindowChrome;
  nsresult rv = mWindowCreator->CreateChromeWindow(
      aParentChrome, aChromeFlags, aOpenWindowInfo, &cancel,
      getter_AddRefs(newWindowChrome));

  if (NS_SUCCEEDED(rv) && cancel) {
    newWindowChrome = nullptr;
    return NS_ERROR_ABORT;
  }

  newWindowChrome.forget(aResult);
The mWindowCreator->CreateChromeWindow() call there is important, because that's the line calling the method which we've hacked around with. I carefully arranged things so that the method would leave NS_SUCCEEDED(rv) as true, so it must be the last parameter which is returning null.

So finally we reach high enough up the stack that we're in the EmbedLite code, and the reason for the null return is immediately clear from looking at the WindowCreator::CreateChromeWindow() implementation. In this method it's the _retval variable that's of interest and the code I added causes the method to return before it gets set.
  if (isForPrinting) {
    return NS_OK;
  }

  mChild->CreateWindow(parentID, reinterpret_cast<uintptr_t>
    (parentBrowsingContext.get()), aChromeFlags, &createdID, aCancel);
[...]
  // check to make sure that we made a new window
  if (_retval) {
      NS_ADDREF(*_retval = browser);
      return NS_OK;
  }
We're going to need a better way to solve this. Unfortunately that won't happen this evening as I have a very early start tomorrow, so I'm going to have to leave it there for today. Still, this will be a good place — with something tangible — to pick up from in the morning.

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