Open Bug 1611585 Opened 5 years ago Updated 2 years ago

Can't interact with chromeonly methods on content documents in multiprocess browser toolbox

Categories

(DevTools :: Console, defect, P2)

defect

Tracking

(Fission Milestone:Future)

Fission Milestone Future

People

(Reporter: Gijs, Unassigned)

References

(Blocks 1 open bug)

Details

(Whiteboard: dt-fission-future)

Attachments

(1 file)

As far as I can tell, the new multiprocess browser toolbox implementation uses the principal of the document to decide how to inspect it / how to interact with it.

This is generally a sensible strategy, but in the browser toolbox it makes it impossible to work with things like our builtin video controls or to check from the console what the result of calling ChromeOnly APIs would be. Some examples:

STR:

  1. open https://codepen.io/ksy36/pen/dyPaopz
  2. open multiprocess browser toolbox
  3. try to inspect the video controls on the video in the bottom pane

ER:
you can inspect them.

AR:
you cannot; they don't show up in the inspector's DOM tree

STR:

  1. open any webpage
  2. open multiprocess browser toolbox
  3. inspect any element on the page
  4. press [esc] to open the console
  5. evaluate $0.ownerDocument.defaultView.windowUtils

ER:
you get a reference to the window utils object

AR:
undefined, or, if the node is in a cross-origin subframe like in the first set of STR, Error: Permission denied to access property "ownerDocument"

Thanks

Hi Gijs,

For the first part of your report, to inspect the video controls which are under the shadow root, I believe you need to flip this pref: devtools.inspector.showUserAgentShadowRoots.

If you want to inspect with the browser toolbox you need to flip that in the browser toolbox's profile (it has a different profile than the browser).

One way to do so:

  • Open the multiprocess browser toolbox
  • Click the DevTools settings meatball menu (...) and pick Documentation
  • In the window that opens, navigate to about:config and flip the pref: devtools.inspector.showUserAgentShadowRoots

(Aside, we should make a direct option in the DevTools settings)

You may have to restart the browser toolbox. Afterwards, you should be able to inspect/navigate into the shadow DOM of the <video> element using the browser toolbox Inspector.

Please let me know if this solves your issue.

With regards to the second part of your report regarding the console, I will ping Nicolas to confirm whether this functionality is expected to work yet or not. It's part of the Fission work, but I don't know if we got to the stage of supporting commands referencing $0 in the right context.

Flags: needinfo?(nchevobbe)

Setting pref devtools.inspector.showUserAgentShadowRoots to true

(In reply to Razvan Caliman [:rcaliman] from comment #2)

For the first part of your report, to inspect the video controls which are under the shadow root, I believe you need to flip this pref: devtools.inspector.showUserAgentShadowRoots.

Thanks. this wasn't obvious and if there was ever a memo I must have missed it. :-)
Why is flipping showAllAnonymousContent not good enough here? Do we really need more than one pref?

(In reply to :Gijs (he/him) from comment #4)

(In reply to Razvan Caliman [:rcaliman] from comment #2)

For the first part of your report, to inspect the video controls which are under the shadow root, I believe you need to flip this pref: devtools.inspector.showUserAgentShadowRoots.

Thanks. this wasn't obvious and if there was ever a memo I must have missed it. :-)
Why is flipping showAllAnonymousContent not good enough here? Do we really need more than one pref?

It was added in https://bugzilla.mozilla.org/show_bug.cgi?id=1483660 and there's some discussion in the thread about if we should reuse showAllAnonymousContent, but I don't see any strong opinions one way or another there. IMO we should fold them together at this point to make things simpler for Firefox devs, unless if there's something I'm not considering (which Julian may be aware of).

Flags: needinfo?(jdescottes)
Depends on: 1613773

No strong reason to keep the preferences separated.
Filed Bug 1613773 since this Bug also described a second unrelated STR. (maybe we should rename the bug and move to console?)

Flags: needinfo?(jdescottes)

Yes, let's move this bug to the console module since there's seem to be an issue with $0 here.

Component: Inspector → Console
Flags: needinfo?(nchevobbe)
Priority: -- → P2
Summary: Can't inspect browser-inserted anonymous content in multiprocess browser toolbox, or interact with chromeonly methods on content documents → Can't interact with chromeonly methods on content documents in multiprocess browser toolbox
Whiteboard: dt-fission-m2-mvp

Tracking dt-fission-m2-mvp bugs for Fission Nightly (M6) milestone

Fission Milestone: --- → M6

This reminds me bug 1027310.

Being able to execute from system compartment when debugging content document is an old request.
It was easier a few years ago when we we just using eval or evalInSubScript.
Now we always go through the Debugger API and it isn't clear if existing Debugger API allow such behavior.

I imagine such feature would require a few tweak to this code:

  let result;
  if (frame) {
    result = frame.evalWithBindings(string, bindings, evalOptions);
  } else {
    result = dbgWindow.executeInGlobalWithBindings(
      string,
      bindings,
      evalOptions
    );
  }
  const dbgWindow = dbg.makeGlobalObjectReference(webConsole.evalWindow);

  // If we have an object to bind to |_self|, create a Debugger.Object
  // referring to that object, belonging to dbg.
  if (!options.selectedObjectActor) {
    return { bindSelf: null, dbgWindow };
  }

  const actor = webConsole.actor(options.selectedObjectActor);

  if (!actor) {
    return { bindSelf: null, dbgWindow };
  }

  const jsVal = actor instanceof LongStringActor ? actor.str : actor.rawValue();
  if (!isObject(jsVal)) {
    return { bindSelf: jsVal, dbgWindow };
  }

  // If we use the makeDebuggeeValue method of jsVal's own global, then
  // we'll get a D.O that sees jsVal as viewed from its own compartment -
  // that is, without wrappers. The evalWithBindings call will then wrap
  // jsVal appropriately for the evaluation compartment.
  const bindSelf = dbgWindow.makeDebuggeeValue(jsVal);
  return { bindSelf, dbgWindow };

Logan, Do you have any idea how we could possibly implement this?
Could executeInGlobalWithBindings be forced to execute via the system compartment?
Or should we provide a special dbgWindow which would be the content window, but somehow wrapped from the system compartment?
Or something else...?

Otherwise, while this feature would be a really nice addition for the browser toolbox, it feels out of scope for M2.
We are now rather trying to focus on delivering Fission support for DevTools against tab rather than improving the Browser Toolbox.

Flags: needinfo?(loganfsmyth)
Whiteboard: dt-fission-m2-mvp → dt-fission-m2-reserve

Could executeInGlobalWithBindings be forced to execute via the system compartment?

My impression was that [ChromeOnly] was something that took affect based on the itself, not the code accessing the object, so when you do $0.ownerDocument.defaultView.windowUtils it is undefined as expected because the document is not a Chrome document. Am I right about that? I wouldn't generally expect that the properties exposed from an object would change based on the permissions of who is doing the viewing, and I'd be surprised if there were a way to do that.

Or should we provide a special dbgWindow which would be the content window, but somehow wrapped from the system compartment?

I guess technically the console could wrap every value and provide a membrane that provided extra functionality like somehow injecting additional properties into an object, but to me that seems exceedingly complex, error-prone, and like a generally strange mental model to have to teach people.

Or something else...?

If the objective is to run Chrome code from the console but interact with a content window, what I'd expect devtools to do would be to have a selector that allows the developer to choose to execute console commands in a specific realm in the target like we allow users to select iframes now. I think for a fully-functional console that's probably necessary anyway since any number of realms could exist within a specific debugging session. Then instead of $0.ownerDocument.defaultView.windowUtils you could explicitly select "JSM Realm" for the console realm and run ChromeUtils.import("WindowUtils.jsm").utilsForWindow($0.ownerDocument.defaultView) to get the utilities by running Chrome-permissioned code while passing in a cross-compartment wrapper for the content window object.

That would require:

  1. Users can choose a realm for the console command to run in
  2. $0 and such need to handle wrapping and unwrapping objects across compartments
Flags: needinfo?(loganfsmyth)

(In reply to Logan Smyth [:loganfsmyth] from comment #10)

Could executeInGlobalWithBindings be forced to execute via the system compartment?

My impression was that [ChromeOnly] was something that took affect based on the itself, not the code accessing the object, so when you do $0.ownerDocument.defaultView.windowUtils it is undefined as expected because the document is not a Chrome document. Am I right about that? I wouldn't generally expect that the properties exposed from an object would change based on the permissions of who is doing the viewing, and I'd be surprised if there were a way to do that.

Here is the definition of ChromeOnly.
will automatically check whether the caller script has the system principal (is chrome or a worker started from a chrome page)
So, it is a little bit of both. Depending on script principal, the object will change to either be:

  • the "real"/"not proxified in any way" Window object from the content page, if the script principal is the content one,
    or,
  • a Xray, if the script principal is the system principal.

Or something else...?

If the objective is to run Chrome code from the console but interact with a content window, what I'd expect devtools to do would be to have a selector that allows the developer to choose to execute console commands in a specific realm in the target like we allow users to select iframes now.

Yes, I was having something similar to this in mind. But instead of it being yet another element(s) in the selector, I was rather seeing a checkbox, only available when you are on a content principal. This checkbox would allow to run against the same content global, but via system principal/realm.

I think for a fully-functional console that's probably necessary anyway since any number of realms could exist within a specific debugging session. Then instead of $0.ownerDocument.defaultView.windowUtils you could explicitly select "JSM Realm" for the console realm and run ChromeUtils.import("WindowUtils.jsm").utilsForWindow($0.ownerDocument.defaultView) to get the utilities by running Chrome-permissioned code while passing in a cross-compartment wrapper for the content window object.

I think you are confused here. ChromeOnly will automatically appear on xrays, so you shouldn't need utilsForWindow helper. Or if you need such helper, it means that are are somewhat trying to expose xrays to content principal scope, which kind of defeat/duplicate the role of xrays.

Getting back to my original question:

Could executeInGlobalWithBindings be forced to execute via the system compartment?

Looking at your response, it sounds like, in the current state, we can't do that?

a Xray, if the script principal is the system principal.

Right! Since we were talking about executing in the context of the content global itself, I hadn't really thought about cross-compartment wrappers coming into play.

I think you are confused here. ChromeOnly will automatically appear on xrays, so you shouldn't need utilsForWindow helper. Or if you need such helper, it means that are are somewhat trying to expose xrays to content principal scope, which kind of defeat/duplicate the role of xrays.

You're right I was confused, I forgot that xray wrappers were a thing because honestly when I said "exceedingly complex, error-prone, and like a generally strange mental model" about membranes above, that's what Xray wrappers are, and I continue stand by calling them confusing.

Looking at your response, it sounds like, in the current state, we can't do that?

I don't think there's an easy way that I can see to do that and I'm not sure it would really make sense in the context of how wrappers work. If you want to execute something in a chrome context, you should have that chrome context as a debuggee and be evaling in that global. So given what we've now said about wrappers, you could for instance do:

dbg.makeGlobalObjectReference(jsmChromeGlobal).executeInGlobalWithBindings("$0.ownerDocument.defaultView.windowUtils", { "$0": nodeDO })

I think it would work the way you're expecting it to work because it will end up wrapped.

But instead of it being yet another element(s) in the selector, I was rather seeing a checkbox, only available when you are on a content principal. This checkbox would allow to run against the same content global, but via system principal/realm.

I think the difficulty here is that you don't run code with a given principal, you run code in a realm that has a principal, if I understand right. The $0 would always represent an object in the content currently viewed in the inspector, potentially wrapped with an xray wrapper, but the code snippet in the console would need to be executed in the JSM realm, which has the system principal. Given that, it's not the principal you are choosing, it's the realm. If it is a checkbox, it seems like it would be extremely easy for users to misunderstand and then not know why their console isn't behaving like they expect.

(In reply to Logan Smyth [:loganfsmyth] from comment #12)

Looking at your response, it sounds like, in the current state, we can't do that?

I don't think there's an easy way that I can see to do that and I'm not sure it would really make sense in the context of how wrappers work. If you want to execute something in a chrome context, you should have that chrome context as a debuggee and be evaling in that global. So given what we've now said about wrappers, you could for instance do:

dbg.makeGlobalObjectReference(jsmChromeGlobal).executeInGlobalWithBindings("$0.ownerDocument.defaultView.windowUtils", { "$0": nodeDO })

I think it would work the way you're expecting it to work because it will end up wrapped.

I tried a bit this path, but hit the issue of "debugee can't be in the same compartment as debugger".
Doing that would require to load DevTools in another distinct system compartment. But may be that's something we have to live with until we possibly revisit this restriction.

But instead of it being yet another element(s) in the selector, I was rather seeing a checkbox, only available when you are on a content principal. This checkbox would allow to run against the same content global, but via system principal/realm.

I think the difficulty here is that you don't run code with a given principal, you run code in a realm that has a principal, if I understand right. The $0 would always represent an object in the content currently viewed in the inspector, potentially wrapped with an xray wrapper, but the code snippet in the console would need to be executed in the JSM realm, which has the system principal. Given that, it's not the principal you are choosing, it's the realm. If it is a checkbox, it seems like it would be extremely easy for users to misunderstand and then not know why their console isn't behaving like they expect.

Realm... Principals... The concept of the two changed recently for me via bug 1357862. I probably mention compartment more than realm because I used to work more with compartments rather than realms. Feel free to replace any mention of compartment by realm.
My point is that there is mostly one realm/compartment we care about, the system one.
The checkbox may actually not even exist and we may execute via system realm/compartment by default from the Browser Toolbox. See comment 0 second STR. Gijs would assume this behavior to be the default one.

But for me, this is clearly a boolean flag, we either:

  • keep things as it works today
  • in the browser toolbox, optionaly or by default, execute through the system compartment/realm, so that we can have xrays, see the same expandos, and have access to ChromeOnly methods.

Now, yes, in theory, we might want to use any arbitrary realm, but I'm not sure there is any user request for it, and it would make the UI really complex with a matrix of all the globals versus all the realms.

I tried a bit this path, but hit the issue of "debugee can't be in the same compartment as debugger".
Doing that would require to load DevTools in another distinct system compartment. But may be that's something we have to live with until we possibly revisit this restriction.

Hard to say the specific cause without more of an example, but you're right that this would be a concern. I was under the impression that in the context of the browser toolbox, the JSM global realm would already be a debuggee, but I guess that varies depending on the target type and which type of server we're connected to.

My point is that there is mostly one realm/compartment we care about, the system one.

For the browser toolbox, there'd be one JSM realm Chrome-permissioned realm that we care about most per-process, but for instance you might want to select the JSM scope of a specific child process to run a command in the context of. Right now we rely on switching to the debugger to use its thread list to select this, but really it should be the console itself that chooses the global of console-executed strings.

The checkbox may actually not even exist and we may execute via system realm/compartment by default from the Browser Toolbox.

I think the parent-process JSM global is a reasonable default global to choose, but once you have to start interacting with content-process objects in the console, that becomes a lot less obvious, and by not having realm selector, the console itself has no way to reflect the realm that you'll be executing in if you run a console command at a particular point.

Now, yes, in theory, we might want to use any arbitrary realm, but I'm not sure there is any user request for it, and it would make the UI really complex with a matrix of all the globals versus all the realms.

Did you mean something other that "all the globals", since a global is a realm?

But for me, this is clearly a boolean flag, we either:

I think I misunderstood what you had in mind when you mentioned a boolean flag. If this a flag within the server itself, enabled for the browser-toolbox server, that says "use the jsm global for console eval by default" then I think that's fine. When you mentioned a flag I was thinking specifically about like checkbox in the devtools UI to enable "chrome-permission eval" or something.

so that we can have xrays, see the same expandos, and have access to ChromeOnly methods.

Don't xray wrappers hide expandos, so by opting into chrome-only access, you'd be opting out of the ability to see expando properties?

I think in general with this issue, I'm struggling to differentiate between what would be in my opinion ideal long-term, and what is actually achievable short-term. I'm also almost certainly talking about things that I simply don't have the full context on, so I hope you'll forgive me for that. I'm also very much a power user, so I'd love to be able to just choose what realm I can run a console command in.

(In reply to Logan Smyth [:loganfsmyth] from comment #14)

But for me, this is clearly a boolean flag, we either:

I think I misunderstood what you had in mind when you mentioned a boolean flag. If this a flag within the server itself, enabled for the browser-toolbox server, that says "use the jsm global for console eval by default" then I think that's fine. When you mentioned a flag I was thinking specifically about like checkbox in the devtools UI to enable "chrome-permission eval" or something.

From a user perspective, I would see the checkbox as implicit in the sense that if I opt to use the browser toolbox (rather than "normal" devtools), I'm opting into system principal evaluation.

so that we can have xrays, see the same expandos, and have access to ChromeOnly methods.

Don't xray wrappers hide expandos, so by opting into chrome-only access, you'd be opting out of the ability to see expando properties?

Yes, as a user I'd have to manually unwrap to see the expandos. But that is possible with Cu.waiveXrays in this case; it's not possible to do the inverse, ie if I'm evaluating with a non-system-principal compartment/realm, I (of course!) cannot escalate from inside that scope to see the chrome-only things.

I think in general with this issue, I'm struggling to differentiate between what would be in my opinion ideal long-term, and what is actually achievable short-term. I'm also almost certainly talking about things that I simply don't have the full context on, so I hope you'll forgive me for that. I'm also very much a power user, so I'd love to be able to just choose what realm I can run a console command in.

There's a picker now, which chooses processes + worker scopes, in the browser toolbox's console. I don't know how easy it'd be to update that to be some kind of nested menu with realms, but yes, that'd help for (if you forgive the expression) "people like us". We would probably want to hide some of the complexity if we show that picker in the web console with fission enabled though...

(In reply to :Gijs (he/him) from comment #15)

There's a picker now, which chooses processes + worker scopes, in the browser toolbox's console. I don't know how easy it'd be to update that to be some kind of nested menu with realms, but yes, that'd help for (if you forgive the expression) "people like us". We would probably want to hide some of the complexity if we show that picker in the web console with fission enabled though...

Would you benefit from using a realm other than the shared system principal one? Supporting more than just that sounds like a quite rare edge cage to me, and would require some decent amount of work to make it right.

Otherwise, the following hack demonstrates comment 12 suggestion dbg.makeGlobalObjectReference(jsmChromeGlobal).executeInGlobalWithBindings("$0.ownerDocument.defaultView.windowUtils", { "$0": nodeDO }):

diff --git a/devtools/server/actors/webconsole/eval-with-debugger.js b/devtools/server/actors/webconsole/eval-with-debugger.js
index 9d24ec2be8ef..3f6f17bc0eab 100644
--- a/devtools/server/actors/webconsole/eval-with-debugger.js
+++ b/devtools/server/actors/webconsole/eval-with-debugger.js
@@ -224,6 +224,11 @@ function getEvalResult(
   if (frame) {
     result = frame.evalWithBindings(string, bindings, evalOptions);
   } else {
+    const ChromeUtils = require("ChromeUtils");
+    const { Cu } = require("chrome");
+    const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+    bindings.window = dbgWindow;
+    dbgWindow = dbg.makeGlobalObjectReference(Cu.getGlobalForObject(Services));
     result = dbgWindow.executeInGlobalWithBindings(
       string,
       bindings,

It is interesting to see that:

  1. we do not hit the "debuggee must be in another compartment than its debugger" exception. Mostly because we don't try to add the JSM global as a new debugee. We only use makeGlobalObjectReference and not addDebuggee.
  2. it demonstrates that it doesn't expose document globals. So you would have to use bindings argument to pass all document globals. I did it here only for the window global. But we miss "document" and many others.
    (3) many things are broken, like autocompletion and it throws a lot. But simple evaluation like window.docShell works)

We only use makeGlobalObjectReference and not addDebuggee.

Good, I was hoping that addDebuggee wouldn't be necessary, but I was not 100% sure if that case was supported.

bindings.window = dbgWindow;

This is the part that made me want a realm selector instead of an executing in the JSM realm but trying to quietly make it behave kind of like you're inside the content document. For instance in the console, something like a = 4 would create a new global called a, and in this case that global will be created on the JSM global, not on the window global. In the general case, injecting window seems super confusing because if you're executing code in the JSM realm, there is no one window, there are many. If we want to expose something like the selected window, I'd expect that to be a special binding like $window so it's more clearly a special-case of the inspector's selected element's window. I think it makes sense for $0 because those already have behaviors that are relatively well-understood for users of the devtools.

Honestly the more I think about this the more I lean back to really wanting a realm selector that, for each process, shows the JSM realm, and any window frames. I don't think trying to quietly use the JSM realm is ever going to be transparent enough to not confuse people in all kinds of ways.

We only use makeGlobalObjectReference and not addDebuggee.

Thinking on this more, I really don't know how to feel about this actually. I really think if we're executing in the JSM realm, it should be a debuggee, and if it's not, we're gonna end up with really confusing behavior because if you run code in the console, you'll potentially be calling code that isn't visible in the debugger's source tree, and things like debugger; and throw new Error() and such won't cause the debugger to pause.

I appreciate that there's a desire to simplify the user experience by making it seem like you're executing code inside some specific window, but if we want to execute code with Chrome permissions, I really think

  1. That realm needs to be a debuggee
  2. If accessing a specific window is wanted, it should be done explicitly via a binding with a clear devtools-associated name like $window or by using the existing $0-like bindings
  3. We should not attempt to hide the fact that your console code runs in the JSM realm

So if you select a specific frame in the browser toolbox, that would mean that you're explicitly choosing to run code in the context of that realm with that realm's normal non-Chrome permissions, and if you want Chrome permissions, you need to access that by selecting the JSM realm of that process, and accessing the specific window realm by something like $0 or else we expose some explicit devtools functions to make it easier to save references to objects and reference them later once you've switched to the JSM realm with the selector.

Right now, could we use the frame message manager global/realm/whatever for a given window? It'll go away when message managers get completely replaced with actors and then we'll need something else, but we can cross that bridge when we get there. It comes with window, and docShell getters and such, and it's more or less what you got when basing things off one of the tabs variable entries in the browser content toolbox. And yes, it should already be a debuggee as far as the debugger is concerned.

dt-fission-m2-reserve bugs do not need to block Fission Nightly (M6). For now, let's track them for Fission riding the trains to Beta (M7) so we revisit these bugs before we ship Fission.

Fission Milestone: M6 → M7

Bulk move of all dt-fission-m2-reserve bugs to Fission MVP milestone.

Fission Milestone: M7 → MVP
Whiteboard: dt-fission-m2-reserve → dt-fission-m3-reserve

Moving "dt-fission-m3-reserve" bugs to "dt-fission-future" because they don't block Fission MVP.

Fission Milestone: MVP → Future
Whiteboard: dt-fission-m3-reserve → dt-fission-future
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: