flypig.co.uk

List items

Items from the current list are shown below.

Blog

14 Jul 2024 : Day 288 #
I'm still continuing today to try to find out what's causing an error coming from the AudioPlayback window actor that's triggered by granting a Web page permission to use the microphone.

To get to the bottom of this, I think it'll be helpful to understand a bit better about how it's all structured. As a window actor, there isn't any code that explicitly constructs an instance of the AudioPlaybackParent JavaScript class where the error is coming from. Instead the Parent and Child pair that makes up the AudioPlayback actor is listed in the ActorManagerParent.jsm file, like this:
  AudioPlayback: {
    parent: {
      moduleURI: "resource://gre/actors/AudioPlaybackParent.jsm",
    },

    child: {
      moduleURI: "resource://gre/actors/AudioPlaybackChild.jsm",
      observers: ["audio-playback"],
    },

    allFrames: true,
  },
This is then used to instantiate the two classes and hook them together and register the child class as a listener for the messages given in the observers list. In this case, the actor is only interested in audio-playback messages. The messages get picked up by the child, which then sends further messages to the parent to act on them.

The child class does some conversion of the messages. Here's the code from which audio-playback messages are received and AudioPlayback messages are sent out.
class AudioPlaybackChild extends JSWindowActorChild {
  observe(subject, topic, data) {
    dump("AudioPlayback: observe()\n");
    dump("AudioPlayback: topic: " + topic + "\n");
    dump("AudioPlayback: data: " + data + "\n");

    var stackTrace = Error().stack;
    dump("Stacktrace: " + stackTrace + "\n");

    if (topic === "audio-playback") {
      let name = "AudioPlayback:";
      if (data === "activeMediaBlockStart") {
        name += "ActiveMediaBlockStart";
      } else if (data === "activeMediaBlockStop") {
        name += "ActiveMediaBlockStop";
      } else {
        name += data === "active" ? "Start" : 
    "Stop";
      }   
      this.sendAsyncMessage(name);
    }   
  }
}
In order to try to understand what's going on I've inserted some debug printouts to this method (the lines that call dump()). This should tell us exactly when the observe() method gets called. And when it doesn't.

Having received such a message the child method then sends a message to the parent, which is where the error is triggered. I've added some additional debug output to the parent class as well. This is the version from ESR 91, but I've added similar annotations to the ESR 78 version too.
class AudioPlaybackParent extends JSWindowActorParent {
  constructor() {
    super();
    this._hasAudioPlayback = false;
    this._hasBlockMedia = false;
  }
  receiveMessage(aMessage) {
    dump("AudioPlayback: receiveMessage()\n");
    dump("AudioPlayback: aMessage: " + aMessage.name + 
    "\n");

    var stackTrace = Error().stack;
    dump("Stacktrace: " + stackTrace + "\n");

    const browser = this.browsingContext.top.embedderElement;
    switch (aMessage.name) {
      case "AudioPlayback:Start":
        this._hasAudioPlayback = true;
        browser.audioPlaybackStarted();
        break;
[...]
    }
  }
}
When I execute this on ESR 91 all of the console output is generated at exactly the time and in exactly the way that's expected:
library "libandroidicu.so" needed or dlopened by "/system/lib64/
    libmedia.so" is not accessible for the namespace "(default)"
AudioPlayback: observe()
AudioPlayback: topic: audio-playback
AudioPlayback: data: active
Stacktrace: observe@resource://gre/actors/AudioPlaybackChild.jsm:15:22

AudioPlayback: receiveMessage()
AudioPlayback: aMessage: AudioPlayback:Start
Stacktrace: receiveMessage@resource://gre/actors/AudioPlaybackParent.jsm:19:22

JavaScript error: resource://gre/actors/AudioPlaybackParent.jsm, line 26: 
    TypeError: browser is null
AudioPlayback: observe()
AudioPlayback: topic: audio-playback
AudioPlayback: data: inactive-pause
Stacktrace: observe@resource://gre/actors/AudioPlaybackChild.jsm:15:22

AudioPlayback: receiveMessage()
AudioPlayback: aMessage: AudioPlayback:Stop
Stacktrace: receiveMessage@resource://gre/actors/AudioPlaybackParent.jsm:19:22

JavaScript error: resource://gre/actors/AudioPlaybackParent.jsm, line 30: 
    TypeError: browser is null
All of this output can be matched up with the child and parent AudioPlayback classes and, as we can see, the end result is the same null browser element error that we saw before.

Since the way in to all this is via the sending of an audio-playback message, the next thing I want to know is where this gets sent from. It turns out there's only one place where this happens and that's in the AudioPlaybackRunnable class that can be found in AudioChannelService.cpp.

I've added breakpoints to the AudioPlaybackRunnable constructor and to the AudioPlaybackRunnable::Run() method to check whether they get used. On ESR 91, when pressing the "Microphone" button, after agreeing to the permissions request, the breakpoint then hits:
(gdb) break AudioPlaybackRunnable::Run
Breakpoint 2 at 0x7ff3c3ab1c: file dom/audiochannel/AudioChannelService.cpp, 
    line 45.
(gdb) break AudioPlaybackRunnable::AudioPlaybackRunnable
Breakpoint 3 at 0x7ff3c3b714: file include/c++/8.3.0/bits/atomic_base.h, line 
    256.
(gdb) c
Continuing.
[...]
Thread 10 "GeckoWorkerThre" hit Breakpoint 3, (anonymous namespace)::
    AudioPlaybackRunnable::AudioPlaybackRunnable (
    aReason=mozilla::dom::AudioChannelService::eDataAudibleChanged, 
    aActive=true, aWindow=0x7fb85d1640, this=0x7fb861eaf0)
    at dom/audiochannel/AudioChannelService.cpp:43
43              mReason(aReason) {}
(gdb) bt
#0  (anonymous namespace)::AudioPlaybackRunnable::AudioPlaybackRunnable (
    aReason=mozilla::dom::AudioChannelService::eDataAudibleChanged, 
    aActive=true, 
    aWindow=0x7fb85d1640, this=0x7fb861eaf0)
    at dom/audiochannel/AudioChannelService.cpp:43
#1  mozilla::dom::AudioChannelService::AudioChannelWindow::
    NotifyAudioAudibleChanged (this=this@entry=0x7fb8e450a0, 
    aWindow=0x7fb85d1640, 
    aAudible=aAudible@entry=mozilla::dom::AudioChannelService::eAudible, 
    aReason=aReason@entry=mozilla::dom::AudioChannelService::
    eDataAudibleChanged)
    at dom/audiochannel/AudioChannelService.cpp:580
[...]
#35 0x0000007fef54989c in ?? () from /lib64/libc.so.6
(gdb) c
Continuing.

Thread 10 "GeckoWorkerThre" hit Breakpoint 2, (anonymous namespace)::
    AudioPlaybackRunnable::Run (this=0x7fb861eaf0)
    at dom/audiochannel/AudioChannelService.cpp:45
45        NS_IMETHOD Run() override {
(gdb) bt
#0  (anonymous namespace)::AudioPlaybackRunnable::Run (this=0x7fb861eaf0)
    at dom/audiochannel/AudioChannelService.cpp:45
#1  0x0000007ff19a4e50 in mozilla::RunnableTask::Run (this=0x7fb956a580)
    at ${PROJECT}/obj-build-mer-qt-xr/dist/include/mozilla/RefPtr.h:313
[...]
#34 0x0000007fef54989c in ?? () from /lib64/libc.so.6
(gdb) c
Continuing.
AudioPlayback: observe()
AudioPlayback: topic: audio-playback
AudioPlayback: data: active
Stacktrace: observe@resource://gre/actors/AudioPlaybackChild.jsm:15:22

udioPlayback: receiveMessage()
AudioPlayback: aMessage: AudioPlayback:Start
Stacktrace: receiveMessage@resource://gre/actors/AudioPlaybackParent.jsm:19:22

JavaScript error: resource://gre/actors/AudioPlaybackParent.jsm, line 26: 
    TypeError: browser is null
Directly after this the page gives the option to stop recording. If I then press on this the breakpoints trigger again in a similar way:
Thread 10 "GeckoWorkerThre" hit Breakpoint 3, (anonymous namespace)::
    AudioPlaybackRunnable::AudioPlaybackRunnable (
    aReason=mozilla::dom::AudioChannelService::ePauseStateChanged, 
    aActive=false, aWindow=0x7fb85d1640, this=0x7fb8d105c0)
    at dom/audiochannel/AudioChannelService.cpp:43
43              mReason(aReason) {}
(gdb) c
Continuing.

Thread 10 "GeckoWorkerThre" hit Breakpoint 2, (anonymous namespace)::
    AudioPlaybackRunnable::Run (this=0x7fb8d105c0)
    at dom/audiochannel/AudioChannelService.cpp:45
45        NS_IMETHOD Run() override {
(gdb) c
Continuing.
AudioPlayback: observe()
AudioPlayback: topic: audio-playback
AudioPlayback: data: inactive-pause
Stacktrace: observe@resource://gre/actors/AudioPlaybackChild.jsm:15:22

AudioPlayback: receiveMessage()
AudioPlayback: aMessage: AudioPlayback:Stop
Stacktrace: receiveMessage@resource://gre/actors/AudioPlaybackParent.jsm:19:22

JavaScript error: resource://gre/actors/AudioPlaybackParent.jsm, line 30: 
    TypeError: browser is null
This all makes sense and is as expected. But the flow seems to diverge from what we see with ESR 78. There the C++ methods get called just as before. But as you can see in the output below, this never translates into the problematic JavaScript being executed. The following shows the breakpoint being hit when the "Microphone" button is pressed and the user then agrees to grant the site access to the microphone. Crucially, although the breakpoints are hit, the debug output added to the AudioPlayback classes are never triggered.
Thread 10 "GeckoWorkerThre" hit Breakpoint 1, 0x0000007fbbbc47e0 in (
    anonymous namespace)::AudioPlaybackRunnable::AudioPlaybackRunnable (
    aReason=<optimized out>, aActive=<optimized out>, aWindow=<optimized out>, 
    this=<optimized out>)
    at obj-build-mer-qt-xr/dist/include/mozilla/cxxalloc.h:33
33      obj-build-mer-qt-xr/dist/include/mozilla/cxxalloc.h: No such file or 
    directory.
(gdb) bt
#0  0x0000007fbbbc47e0 in (anonymous namespace)::AudioPlaybackRunnable::
    AudioPlaybackRunnable (aReason=<optimized out>, aActive=<optimized out>,
    aWindow=<optimized out>, this=<optimized out>)
    at obj-build-mer-qt-xr/dist/include/mozilla/cxxalloc.h:33
#1  mozilla::dom::AudioChannelService::AudioChannelWindow::
    NotifyAudioAudibleChanged (this=this@entry=0x7f80fdd0b0, 
    aWindow=0x7f80cb6060,
    aAudible=aAudible@entry=mozilla::dom::AudioChannelService::eAudible, 
    aReason=aReason@entry=mozilla::dom::AudioChannelService::
    eDataAudibleChanged)
    at dom/audiochannel/AudioChannelService.cpp:592
[...]
#37 0x0000007fb735c89c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/
    clone.S:78
(gdb) c
Continuing.

Thread 10 &quot;GeckoWorkerThre&quot; hit Breakpoint 2, (anonymous namespace)::
    AudioPlaybackRunnable::Run (this=0x7f8137d4f0)
    at dom/audiochannel/AudioChannelService.cpp:44
44        NS_IMETHOD Run() override {
(gdb) bt
#0  (anonymous namespace)::AudioPlaybackRunnable::Run (this=0x7f8137d4f0)
    at dom/audiochannel/AudioChannelService.cpp:44
#1  0x0000007fb9c014c4 in nsThread::ProcessNextEvent (aResult=0x7fa69f3ba7, 
    aMayWait=<optimized out>, this=0x7f8002fd60)
    at xpcom/threads/nsThread.cpp:1211
[...]
#24 0x0000007fb735c89c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/
    clone.S:78
(gdb) c            
Continuing.
[...]
Selecting the "Stop" button on ESR 78 shows a similar pattern: the breakpoints are hit but the JavaScript debug output never appears:
Thread 10 &quot;GeckoWorkerThre&quot; hit Breakpoint 1, 0x0000007fbbbc47e0 in (
    anonymous namespace)::AudioPlaybackRunnable::AudioPlaybackRunnable (
    aReason=<optimized out>, aActive=<optimized out>, aWindow=<optimized out>, 
    this=<optimized out>)
    at obj-build-mer-qt-xr/dist/include/mozilla/cxxalloc.h:33
33      obj-build-mer-qt-xr/dist/include/mozilla/cxxalloc.h: No such file or 
    directory.
(gdb) bt
#0  0x0000007fbbbc47e0 in (anonymous namespace)::AudioPlaybackRunnable::
    AudioPlaybackRunnable (aReason=<optimized out>, aActive=<optimized out>,
    aWindow=<optimized out>, this=<optimized out>)
    at obj-build-mer-qt-xr/dist/include/mozilla/cxxalloc.h:33
#1  mozilla::dom::AudioChannelService::AudioChannelWindow::
    NotifyAudioAudibleChanged (this=this@entry=0x7f80f97430, 
    aWindow=0x7f80b985a0,
    aAudible=aAudible@entry=mozilla::dom::AudioChannelService::eAudible, 
    aReason=aReason@entry=mozilla::dom::AudioChannelService::
    eDataAudibleChanged)
    at dom/audiochannel/AudioChannelService.cpp:592
[...]
#37 0x0000007fb735c89c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/
    clone.S:78
(gdb) c                            
Continuing.

Thread 10 &quot;GeckoWorkerThre&quot; hit Breakpoint 2, (anonymous namespace)::
    AudioPlaybackRunnable::Run (this=0x7f80f52980)
    at dom/audiochannel/AudioChannelService.cpp:44
44        NS_IMETHOD Run() override {
(gdb) bt
#0  (anonymous namespace)::AudioPlaybackRunnable::Run (this=0x7f80f52980)
    at dom/audiochannel/AudioChannelService.cpp:44
#1  0x0000007fb9c014c4 in nsThread::ProcessNextEvent (aResult=0x7fa69f3ba7, 
    aMayWait=<optimized out>, this=0x7f8002fd60)
    at xpcom/threads/nsThread.cpp:1211
[...]
#24 0x0000007fb735c89c in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/
    clone.S:78
(gdb) c
Continuing.
[...]
I'm currently rather baffled by this. Everything works as expected on ESR 91, but while on ESR 78 the messages are very clearly sent from the C++ code, they're never received by the JavaScript actor.

Unfortunately I've run out of time to investigate this further today. But I'll continue looking at it tomorrow, when I'll also have to come to a decision about how to actually tackle the error. Do I add in a check for a null browser variable, remove the AudioPlayback actor entirely, or dig deeper to try to establish the underlying for why the embedder element is never defined. With any luck our investigations tomorrow will lead us to a suitable answer.

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