Closed Bug 1184661 Opened 4 years ago Closed 3 years ago

1,100 instances of "Silently denied access to property (void 0): Access to privileged JS object not permitted (@(null):0:0)" emitted from js/xpconnect/wrappers/XrayWrapper.cpp during linux64 debug testing

Categories

(DevTools :: Debugger, defect)

defect
Not set

Tracking

(firefox51 fixed)

RESOLVED FIXED
Firefox 51
Tracking Status
firefox51 --- fixed

People

(Reporter: erahm, Assigned: ejpbruel)

References

(Depends on 1 open bug, Blocks 1 open bug)

Details

Attachments

(1 file)

> 949 [NNNNN] WARNING: Silently denied access to property |undefined|: Access to privileged JS object not permitted (@resource://gre/modules/commonjs/toolkit/loader.js -> resource://gre/modules/devtools/server/actors/script.js:1133): file js/xpconnect/wrappers/XrayWrapper.cpp, line 208

This warning [1,2] shows up in the following test suites:

> mozilla-central_ubuntu64_vm-debug_test-mochitest-devtools-chrome-4-bm122-tests1-linux64-build9.txt:949

It shows up in 4 tests. A few of the most prevalent:

> 700 - browser/devtools/debugger/test/browser_dbg_break-on-dom-event-01.js
> 150 - browser/devtools/debugger/test/browser_dbg_break-on-dom-event-02.js
> 50 - browser/devtools/debugger/test/browser_dbg_break-on-dom-event-03.js
> 49 - browser/devtools/debugger/test/browser_dbg_break-on-dom-08.js

[1] https://hg.mozilla.org/mozilla-central/annotate/7a00aec32a01/js/xpconnect/wrappers/XrayWrapper.cpp#l208
[2] https://hg.mozilla.org/mozilla-central/annotate/e7e69cc8c07b/toolkit/devtools/server/actors/script.js#l1133
Panos, this seems like it's pointing to a real error, what do you think?
Flags: needinfo?(past)
Hmm, does changing the if() clause like this fix the warnings?

if ("class" in listenerDO && (listenerDO.class == "Object" || listenerDO.class == "XULElement"))

But even if it does, it would just be papering over a platform issue. I can't investigate further in the near future, but perhaps jimb can, if it's a Debugger API issue.
Flags: needinfo?(past)
Component: Developer Tools → Developer Tools: Debugger
This has become more prevalent and morphed a bit, but basically the same tests. Jim, Panos suggested you might be able dig into this further.

> 1115 WARNING: Silently denied access to property (void 0): Access to privileged JS object not permitted (@(null):0:0): file js/xpconnect/wrappers/XrayWrapper.cpp, line 214

This warning [1] shows up in the following test suites:

>   1111 - desktop-test-linux64/debug-mochitest-devtools-chrome-9 dt9
>      1 - desktop-test-linux64/debug-mochitest-chrome-1 c1
>      1 - desktop-test-linux64/debug-mochitest-devtools-chrome-3 dt3
>      1 - desktop-test-linux64/debug-mochitest-e10s-10 10
>      1 - desktop-test-linux64/debug-mochitest-10 10

It shows up in 8 tests. A few of the most prevalent:

>    848 -        devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
>    159 -        devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
>     53 -        devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js
>     51 -        devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js
>      1 -        devtools/client/storage/test/browser_storage_dynamic_updates.js
>      1 -        dom/animation/test/chrome/test_animate_xrays.html
>      1 - [e10s] toolkit/components/extensions/test/mochitest/test_ext_storage.html
>      1 -        toolkit/components/extensions/test/mochitest/test_ext_storage.html

[1] https://hg.mozilla.org/mozilla-central/annotate/9ec789c0ee5b/js/xpconnect/wrappers/XrayWrapper.cpp#l214
Flags: needinfo?(jimb)
Summary: 900 instances of "Silently denied access to property |undefined|: Access to privileged JS object not permitted" emitted during linux64 debug testing → 1,100 instances of "Silently denied access to property (void 0): Access to privileged JS object not permitted (@(null):0:0)" emitted from js/xpconnect/wrappers/XrayWrapper.cpp during linux64 debug testing
So I guess there are a couple of issues here:
 1) Devtools might be doing something wrong (thus triggering the warning)
 2) The warning itself seems to be messed up (not listing file names and properties correctly)
 3) We pump a warning to the browser console once, but always emit a NS_WARNING in debug. We might want to just get rid of that.
An example stack:

> Process 4882 stopped
> * thread #1: tid = 0xd6fe3, 0x0000000102fe7aa1 XUL`xpc::ReportWrapperDenial(cx=0x0000000110b25000, id=<unavailable>, type=WrapperDenialForCOW, reason="Access to privileged JS object not permitted") + 49 at XrayWrapper.cpp:187, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
>     frame #0: 0x0000000102fe7aa1 XUL`xpc::ReportWrapperDenial(cx=0x0000000110b25000, id=<unavailable>, type=WrapperDenialForCOW, reason="Access to privileged JS object not permitted") + 49 at XrayWrapper.cpp:187
>    184  bool
>    185  ReportWrapperDenial(JSContext* cx, HandleId id, WrapperDenialType type, const char* reason)
>    186  {
> -> 187      CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx));
>    188      bool alreadyWarnedOnce = priv->wrapperDenialWarnings[type];
>    189      priv->wrapperDenialWarnings[type] = true;
>    190  
> (lldb) call DumpJSStack()
> 0 ThreadActor<._getAllEventListeners(eventTarget = [object HTMLButtonElement]) ["resource://gre/modules/commonjs/toolkit/loader.js -> resource://devtools/server/actors/script.js":1132]
>     this = [Actor context/server1.conn0.child2/26]
> 1 ThreadActor<._allEventsListener(event = [object UIEvent]) ["resource://gre/modules/commonjs/toolkit/loader.js -> resource://devtools/server/actors/script.js":1095]
>     this = [Actor context/server1.conn0.child2/26]
> 2 bound _allEventsListener([object UIEvent]) ["self-hosted":891]
>     this = [object Window]
> 3 SpecialPowersAPI.prototype.dispatchEvent(aWindow = [object ChromeWindow], target = [object HTMLButtonElement], event = [object MouseEvent]) ["chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js":1763]
>     this = [ChromePowers]
> 4 sendMouseEvent(aEvent = [object Object], aTarget = [object HTMLButtonElement]) ["chrome://mochikit/content/tests/SimpleTest/EventUtils.js":142]
>     this = [object Object]
> 5 triggerButtonClick() ["chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js":217]
>     this = [object ChromeWindow]
> 6 testScope/test_executeSoon/<.run() ["chrome://mochikit/content/browser-test.js":993]
>     this = [object Object]
> 
> (lldb) bt
> * thread #1: tid = 0xd6fe3, 0x0000000102fe7aa1 XUL`xpc::ReportWrapperDenial(cx=0x0000000110b25000, id=<unavailable>, type=WrapperDenialForCOW, reason="Access to privileged JS object not permitted") + 49 at XrayWrapper.cpp:187, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
>   * frame #0: 0x0000000102fe7aa1 XUL`xpc::ReportWrapperDenial(cx=0x0000000110b25000, id=<unavailable>, type=WrapperDenialForCOW, reason="Access to privileged JS object not permitted") + 49 at XrayWrapper.cpp:187
>     frame #1: 0x0000000102ff6a83 XUL`xpc::ExposedPropertiesOnly::deny(act=<unavailable>, id=JS::HandleId @ r14) + 131 at AccessCheck.cpp:443
>     frame #2: 0x0000000102ffe86d XUL`xpc::FilteringWrapper<js::SecurityWrapper<js::CrossCompartmentWrapper>, xpc::ExposedPropertiesOnly>::enter(this=<unavailable>, cx=<unavailable>, wrapper=<unavailable>, id=<unavailable>, act=<unavailable>, bp=0x00007fff5fbf93e1) const + 77 at FilteringWrapper.cpp:175
>     frame #3: 0x00000001070a691c XUL`js::Proxy::className(JSContext*, JS::Handle<JSObject*>) [inlined] js::AutoEnterPolicy::AutoEnterPolicy(cx=<unavailable>, handler=0x0000000108910210, wrapper=<unavailable>, act=1, mayThrow=false) + 63 at Proxy.h:541
>     frame #4: 0x00000001070a68dd XUL`js::Proxy::className(JSContext*, JS::Handle<JSObject*>) [inlined] js::AutoEnterPolicy::AutoEnterPolicy(handler=0x0000000108910210, id=JS::HandleId @ r13, act=1, mayThrow=false) at Proxy.h:540
>     frame #5: 0x00000001070a68dd XUL`js::Proxy::className(cx=0x0000000110b25000, proxy=<unavailable>) + 93 at Proxy.cpp:470
>     frame #6: 0x00000001071402eb XUL`js::DebuggerObject::getClassName(cx=0x0000000110b25000, object=<unavailable>, result=<unavailable>) + 123 at Debugger.cpp:9248
>     frame #7: 0x000000010714018f XUL`js::DebuggerObject::classGetter(cx=0x0000000110b25000, argc=<unavailable>, vp=0x00007fff5fbf9660) + 223 at Debugger.cpp:8243
>     frame #8: 0x00000001071d3d3e XUL`js::CallJSNative(cx=0x0000000110b25000, native=(XUL`js::DebuggerObject::classGetter(JSContext*, unsigned int, JS::Value*) at Debugger.cpp:8239), args=0x00007fff5fbf9628)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) + 286 at jscntxtinlines.h:232
>     frame #9: 0x00000001071c3012 XUL`js::InternalCallOrConstruct(cx=<unavailable>, args=<unavailable>, construct=<unavailable>) + 690 at Interpreter.cpp:441
>     frame #10: 0x00000001071c417d XUL`js::CallGetter(JSContext*, JS::Handle<JS::Value>, JS::Handle<JS::Value>, JS::MutableHandle<JS::Value>) [inlined] js::Call(cx=0x0000000110b25000, args=<unavailable>) + 32 at Interpreter.cpp:517
>     frame #11: 0x00000001071c415d XUL`js::CallGetter(cx=0x0000000110b25000, thisv=JS::HandleValue @ r12, getter=JS::HandleValue @ r13, rval=JS::MutableHandleValue @ r14) + 125 at Interpreter.cpp:631
>     frame #12: 0x00000001071fcbab XUL`bool GetExistingProperty<(js::AllowGC)1>(JSContext*, js::MaybeRooted<JS::Value, (js::AllowGC)1>::HandleType, js::MaybeRooted<js::NativeObject*, (js::AllowGC)1>::HandleType, js::MaybeRooted<js::Shape*, (js::AllowGC)1>::HandleType, js::MaybeRooted<JS::Value, (js::AllowGC)1>::MutableHandleType) + 96 at NativeObject.cpp:1737
>     frame #13: 0x00000001071fcb4b XUL`bool GetExistingProperty<(js::AllowGC)1>(cx=0x0000000110b25000, receiver=js::MaybeRooted<JS::Value, js::AllowGC>::HandleType @ r13, obj=<unavailable>, shape=<unavailable>, vp=<unavailable>)1>::HandleType, js::MaybeRooted<js::NativeObject*, (js::AllowGC)1>::HandleType, js::MaybeRooted<js::Shape*, (js::AllowGC)1>::HandleType, js::MaybeRooted<JS::Value, (js::AllowGC)1>::MutableHandleType) + 811 at NativeObject.cpp:1785
>     frame #14: 0x00000001071fd6fb XUL`bool NativeGetPropertyInline<(js::AllowGC)1>(cx=<unavailable>, obj=<unavailable>, receiver=js::MaybeRooted<JS::Value, js::AllowGC>::HandleType @ 0x00007fff5fbf9748, id=<unavailable>, nameLookup=NotNameLookup, vp=<unavailable>)1>::HandleType, js::MaybeRooted<JS::Value, (js::AllowGC)1>::HandleType, js::MaybeRooted<jsid, (js::AllowGC)1>::HandleType, IsNameLookup, js::MaybeRooted<JS::Value, (js::AllowGC)1>::MutableHandleType) + 2107 at NativeObject.cpp:2012
>     frame #15: 0x00000001070fee95 XUL`js::Wrapper::get(JSContext*, JS::Handle<JSObject*>, JS::Handle<JS::Value>, JS::Handle<jsid>, JS::MutableHandle<JS::Value>) const [inlined] js::GetProperty(cx=<unavailable>, receiver=<unavailable>, id=<unavailable>, vp=<unavailable>) + 197 at NativeObject.h:1478
>     frame #16: 0x00000001070fee54 XUL`js::Wrapper::get(this=<unavailable>, cx=<unavailable>, proxy=<unavailable>, receiver=<unavailable>, id=<unavailable>, vp=<unavailable>) const + 132 at Wrapper.cpp:143
>     frame #17: 0x000000010709e4dc XUL`js::CrossCompartmentWrapper::get(this=0x0000000108c5e158, cx=0x0000000110b25000, wrapper=<unavailable>, receiver=<unavailable>, id=JS::HandleId @ 0x00007fff5fbf9880, vp=JS::MutableHandleValue @ r12) const + 172 at CrossCompartmentWrapper.cpp:205
>     frame #18: 0x00000001070a4cb0 XUL`js::Proxy::get(cx=0x0000000110b25000, proxy=<unavailable>, receiver_=<unavailable>, id=JS::HandleId @ r12, vp=<unavailable>) + 384 at Proxy.cpp:310
>     frame #19: 0x00000001071d4a27 XUL`js::GetProperty(JSContext*, JS::Handle<JSObject*>, JS::Handle<JS::Value>, js::PropertyName*, JS::MutableHandle<JS::Value>) + 39 at NativeObject.h:1477
>     frame #20: 0x00000001071d4a00 XUL`js::GetProperty(cx=0x0000000110b25000, obj=JS::HandleObject @ r12, receiver=JS::HandleValue @ r15, name=<unavailable>, vp=<unavailable>) + 112 at jsobj.h:836
>     frame #21: 0x00000001071c6e4b XUL`js::GetProperty(cx=0x0000000110b25000, v=<unavailable>, name=<unavailable>, vp=<unavailable>) + 587 at Interpreter.cpp:4161
>     frame #22: 0x0000000106e6c490 XUL`js::jit::ComputeGetPropResult(cx=0x0000000110b25000, frame=<unavailable>, op=<unavailable>, name=<unavailable>, val=<unavailable>, res=<unavailable>) + 544 at SharedIC.cpp:2664
>     frame #23: 0x0000000106e54f73 XUL`js::jit::DoGetPropFallback(cx=0x0000000110b25000, payload=<unavailable>, stub_=<unavailable>, val=<unavailable>, res=<unavailable>) + 3363 at SharedIC.cpp:2744
>     frame #24: 0x000000010ff5ce1d
>     frame #25: 0x000000010ff5be1d
>     frame #26: 0x0000000106badc05 XUL`EnterBaseline(cx=0x00000001382ed180, data=0x00000001143ad3d0) + 709 at BaselineJIT.cpp:155
>     frame #27: 0x0000000106bae34b XUL`js::jit::EnterBaselineAtBranch(cx=0x0000000110b25000, fp=0x00000001143ad3f0, pc=<unavailable>) + 875 at BaselineJIT.cpp:262
>     frame #28: 0x00000001071b26a6 XUL`Interpret(cx=0x0000000110b25000, state=0x00007fff5fbfaa10) + 4326 at Interpreter.cpp:1877
>     frame #29: 0x00000001071b14ef XUL`js::RunScript(cx=0x0000000110b25000, state=0x00007fff5fbfaa10) + 511 at Interpreter.cpp:399
>     frame #30: 0x00000001071c2fb1 XUL`js::InternalCallOrConstruct(cx=0x0000000110b25000, args=<unavailable>, construct=<unavailable>) + 593 at Interpreter.cpp:471
>     frame #31: 0x0000000106b7ab40 XUL`js::jit::DoCallFallback(cx=0x0000000110b25000, frame=0x00007fff5fbfadc8, stub_=0x000000012bc75f48, argc=1, vp=0x00007fff5fbfad58, res=<unavailable>) + 1904 at BaselineIC.cpp:5977
>     frame #32: 0x000000010ff633c1
FWIW the suggestion from comment 2 does not bypass the warning.
I'm not able to reproduce this, on a Fedora 64 debug build. Does a command like:

$ mach test devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js

show the problem?

It is a weird error.

(I'm going to be on PTO next week.)
Depends on: 1290255
(In reply to Jim Blandy :jimb from comment #7)
> I'm not able to reproduce this, on a Fedora 64 debug build. Does a command
> like:
> 
> $ mach test
> devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
> 
> show the problem?

Yes. You'll need a debug build.
(In reply to Eric Rahm [:erahm] from comment #8)
> (In reply to Jim Blandy :jimb from comment #7)
> > I'm not able to reproduce this, on a Fedora 64 debug build. Does a command
> > like:
> > 
> > $ mach test
> > devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
> > 
> > show the problem?
> 
> Yes. You'll need a debug build.

Oh right you said it was debug. I can repro on mac, the log spam is from our Ubuntu (12.04?) linux64 test machines. I'll check my 14.04 machine as well.
I can repro on 14.04 as well (technically in a release build, I just set a breakpoint in xpc::ReportWrapperDenial).
Judging by the log files it's only showing up in non-e10s, so the command is:

> $ mach mochitest --disable-e10s devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
I'll be on PTO next week, and I haven't had time to dig into this. Eddy or Nick, would you be able to look at this?
Flags: needinfo?(nfitzgerald)
Flags: needinfo?(jimb)
Flags: needinfo?(ejpbruel)
Guess who's back? Back again. Eddy's back. Tell a friend!

I'll take a stab at this.
Flags: needinfo?(nfitzgerald)
Flags: needinfo?(ejpbruel)
The warning is triggered by calling the DebuggerObject.className getter with a referent that is a proxy to a security wrapper (presumably to a privileged JS object). The particular call that triggers the warning happens at devtools/server/actor/script.js:1132.

Questions I have at this point:
1. What is the object that are we getting the className property for on line 1132 of script.js?
2. Why is this object a proxy to a security wrapper?
3. Since script.js is running in privileged code, why is this triggering a security warning?
Answer to question 3: even though the debugger itself is running as privileged code, DebuggerObject.className enters the compartment of the referent before trying to obtain the className. Apparently, the proxy lives in a non-privileged compartment.
When you call GetObjectClassName to get the className of an object, this function first checks whether the object is a proxy. If it is, GetObjectClassName calls the Proxy::className trap for the proxy.

The Proxy::className trap tries to enter the enter the compartment of the proxy's handler (using JSID_VOIDHANDLE as the id and Action::Get as the action). If entering the compartment is allowed, the Proxy::className trap calls the className method on the proxy's handler.

If entering the compartment is *not* allowed, the Proxy:className trap uses a fallback method that is always safe: if the proxy is callable, return "Function" as the class name. Otherwise, return "Object".

In this particular case, the handler turns out to be a FilteringWrapper (why?). The enter method of this wrapper denies our request to enter the compartment. Moreover, if we are running in debug mode, it reports a warning that an attempt to access a property on the wrapper was silently denied.

Since proxies are apparently designed to cope with situations where the compartment of the handler cannot be entered, does it really make sense to report a warning every time a proxy tries to do so?
Flags: needinfo?(gkrizsanits)
In ThreadActor._getAllEventListeners, we construct a list of handler functions for each target in the event target chain of a given event target. If a handler is an event handler object, we obtain the handler function from its handleEvent property. Otherwise, the handler function is assumed to be the handler itself.

To detect whether a handler is an event handler object, we would check its class
name property. This approach is fragile for several reasons: if the handler is a proxy, accessing its class name may cause the debuggee to run. Moreover, since accessing a proxy's class name requires us to enter the compartment of the
proxy's handler, if the latter is a security wrapper, accessing its class name will cause security warnings in debug mode.
    
Since a handler is either an event handler object or a function, it should be safe to assume that a handler is an event handler object if and only if it is not callable. Checking whether a proxy is callable cannot cause the debuggee to run. Moreover, it does not require us to enter the compartment of the proxy's handler, thus avoiding the security warnings we mentioned earlier.
Assignee: nobody → ejpbruel
Attachment #8776609 - Flags: review?(past)
Comment on attachment 8776609 [details] [diff] [review]
If a listener is not callable, assume it is an event listener object.

Review of attachment 8776609 [details] [diff] [review]:
-----------------------------------------------------------------

Assuming the tests still pass this makes sense, thanks!
Attachment #8776609 - Flags: review?(past) → review+
I had a few minutes today to look into this.

(In reply to Eddy Bruel [:ejpbruel] from comment #14)
> Questions I have at this point:
> 1. What is the object that are we getting the className property for on line
> 1132 of script.js?

Not sure what is it, but something from a system compartment. Seems like a chrome object
was referenced by the content for some reason in which case content should only have
access to its explicitly exposed props (legacy __exposedProps__ stuff). I would guess
it's related to some browser chrome stuff which in case of e10s lives in another process
hence does not show up in the debugger.

> 2. Why is this object a proxy to a security wrapper?

Seems like the referent is a cross compartment wrapper. The cross compartment wrapper lives in the content compartment while the object it refers to lives in a system compartment. So you have an extra level of indirection here. From the debugger perspective I'm quite sure that you want to filter these objects out. You want to have a DO that's referent is the object from the system compartment directly instead of this cross compartment security wrapper that refers to it from the content scope.

Does that make sense?
Flags: needinfo?(gkrizsanits) → needinfo?(ejpbruel)
(In reply to Eddy Bruel [:ejpbruel] from comment #19)
> Try push for this patch:
> https://treeherder.mozilla.org/#/jobs?repo=try&revision=9fbf62195b3f

Try looks happy, the warning has been eliminated.
Pushed by cbook@mozilla.com:
https://hg.mozilla.org/integration/fx-team/rev/dd5ef823250c
If a listener is not callable, assume it is an event listener object. r=past
https://hg.mozilla.org/mozilla-central/rev/dd5ef823250c
Status: NEW → RESOLVED
Closed: 3 years ago
Resolution: --- → FIXED
Target Milestone: --- → Firefox 51
By design, the default behavior for Debugger is to see objects the way content would see them. So although Debugger, as C++ code, has the power to follow any sort of cross-compartment wrapper it likes, it avoids doing so, since that means that answers to questions asked via Debugger get different answers from those code itself would see.

However, it's fine for Debugger to exercise its *caller's* privileges, instead of *content's* privileges, as long as that's not the default. It seems like, for object display, the former might be more appropriate.

(In reply to Gabor Krizsanits [:krizsa :gabor] from comment #20)
> (In reply to Eddy Bruel [:ejpbruel] from comment #14)
> > Questions I have at this point:
> > 1. What is the object that are we getting the className property for on line
> > 1132 of script.js?
> 
> Not sure what is it, but something from a system compartment. Seems like a
> chrome object
> was referenced by the content for some reason in which case content should
> only have
> access to its explicitly exposed props (legacy __exposedProps__ stuff).

I think there's a misunderstanding here. There are no property references involved here. Here's line 1132:

        if (listenerDO.class == "Object" || listenerDO.class == "XULElement") {

These are references to Debugger.Object.prototype.class, which is essentially trying to return JSClass::name:

    http://searchfox.org/mozilla-central/rev/389a3e0817b873a64376ec2b65f6807afbba9d8f/js/public/Class.h#492

The complexity comes from the fact that we'd really prefer not to get "Proxy" or whatever as the class name when we're looking at a wrapper. So in that case we call Proxy::className:

http://searchfox.org/mozilla-central/rev/389a3e0817b873a64376ec2b65f6807afbba9d8f/js/src/proxy/Proxy.cpp#461

which asks the proxy handler to get involved, hence the complexity.

I don't know the details, but I hear the idea of a class name doesn't comport well with ES6, so a lot of this stuff will need to be revisited soon.
This NI looks like it's no longer relevant.
Flags: needinfo?(ejpbruel)
Product: Firefox → DevTools
You need to log in before you can comment on or make changes to this bug.