Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow debugging multiple global objects #57

Open
pavelfeldman opened this issue Jun 21, 2019 · 11 comments
Open

Allow debugging multiple global objects #57

pavelfeldman opened this issue Jun 21, 2019 · 11 comments
Assignees
Labels
feature-request Request for new features or functionality

Comments

@pavelfeldman
Copy link
Member

I'd like to support WebWorkers and iframes in Chromium DA.

Originally, I was going to map WebWorkers to threads in the debug adapter. But those workers run in their own isolates in v8, with their own heaps. So when I serve the evaluate request, I need to know which global object to resolve the expression against.

Adding threadId into the evaluate could solve it for workers, but there is a similar problem with the iframes. In-process iframes belong to the same v8 isolate, same heap, same thread, but they have their own v8 contexts with their own global objects. Same goes for the content scripts / worlds - that's another dimension where every frame can have multiple contexts with their own global objects.

CDP has a notion of the ExecutionContext to address that. CDP execution contexts map to the V8 contexts. There are events firing when contexts are created (for example when iframe or extension is loaded), deleted, etc. These contexts are represented in the console drop down in the Chrome's DevTools.

Is there anything similar in DAP? How should we approach implementing support for iframes & workers?

@gregg-miskelly
Copy link
Member

In case this is helpful to you - most debuggers will keep a mapping between frame ids and some sort of frame object that contains all the information needed to know how evaluations would work if you were writing code at the exact context of that frame. So for example, they would definitely know what thread the frame was from so they could pick the right heap in your example. In our C++ debugger, we have supported a 'context operator' that lets the user type a prefix to their expressions to indicate where the rest of the expression should be evaluated. Example: mylibrary.dll!MyGlobalVariable.

@weinand weinand self-assigned this Jun 24, 2019
@weinand weinand added the feature-request Request for new features or functionality label Jun 24, 2019
@pavelfeldman
Copy link
Member Author

Prefixes make sense, how are these discovered in the C++ world?

In the browser world, it is typical to have numerous contexts identified by their URLs (long strings) and developers have greater expectations in terms of context discoverability and selection. One selects their 'context' and then issues a number of commands in that context to inspect the runtime state.

Knowing that there are similar use cases in C++ makes this request even stronger.

@gregg-miskelly
Copy link
Member

By 'discovered' do you mean how to users know they can do this?

If so - we have just used documentation, and we have given a "tips & tricks" talk for ~20 years, and it is generally listed there. If you think this will be very common, I could also imagine adding some text at the start of debug console which either linked to documentation, or just explained the feature if this is the only "advanced" feature that you have. You could also think of some sort of pesudo-command that can be evaluated in the debug console which could list all of the evaluation contexts (ex: evaluate Debugger.getEvaluationContexts()). In C++, if you are working on the kind of project that has a bunch of modules, and you know the code enough to know what module has the variable you are looking for, then you probably know the module's name. But we also provide a modules window in VS, and in VS Code, module loads are dumped to the console. Another thing you could do, particularly if you pick a context operator which is distinct (ex: {myPage.js}myClass.myProp), is to provide intellisense so that as soon as the user types the { in my example, you know that you are dealing with the context operator, so you can provide intellisense listing all the valid contexts to evaluate.

@pavelfeldman
Copy link
Member Author

I don't think documentation is going to cut it for the web world - ability to evaluate in the context of an iframe has been there for 15 years in Firebug and for 10 years in Chrome DevTools. So there are certain expectations on the client's side at this point, it definitely is not considered as an advanced feature, rather an essential one. The question is whether it could be achieved via dual-purposing threads or it is its own thing. In Chrome it is its own thing within thread.

@PavelSosin
Copy link

@pavelfeldman In the debugger WebGUI hosted by browser and nodejs debugging the option to debug Workers is must. But there is no "Global" cross Worker objects because every Worker is separate process. Workers are not threads They are processes! There are no cross-tabs memory in browsers too. Any attempt of cross worker ans cross tab communication bypassing browser provided mechanisms like debugger is obvious security violation.

@PavelSosin
Copy link

@pavelfeldman
So imagine such console operations sequence:

  1. myPage.yourCreditCardData = yourPage.yourCreditCardData
  2. Execute call PostFunction step in myPage
  3. Bingo - I have your credit card data

@PavelSosin
Copy link

DAP is secure Web protocol by its definition. It has JSONRpc and WebSocket in its communication protocol stack. CDP is also secure Web protocol. Only one security context is allowed - it can't be a prefix. Everything which puts Web security under risk or conflicts with Web security principles can't be a part of DAP.

@PavelSosin
Copy link

In the terms of Chrome debugging protocol the default context is DefaultBrowsercontext. Only explicit switch of BrowserContext is allowed and it is enough for Workers debugging implementation exactly as it works in Chrome debugger. There is no analog in C or C++ or Java debugging because it requires reconnect to another process. maybe somebody knows an example of multiprocess application debugging tools except cloud service mesh debuggers?

@pavelfeldman
Copy link
Member Author

@weinand, @gregg-miskelly, an update for you:

What I ended up doing was introducing the new 'Execution contexts' tree where I listed all the contexts within the threads. When user selects the context, I communicate that to the backend using the custom command. That's basically a slightly better UX than the 'prefix' option. In the screenshot below, you can see the YouTube iframe's context available under the main thread and a service worker thread / context beside it.

Screen Shot 2019-07-16 at 8 24 37 AM

There is one minor issue with this approach: I could have had thread1 selected in the Call Stack view, while thread2's context selected in the execution contexts. If I could listen to the selected thread in the Call Stack view in VSCode, I would be able to only show contexts for the selected thread in my Execution contexts tree.

@gregg-miskelly
Copy link
Member

Yes, I have also wanted a couple of times now. I opened microsoft/vscode#63943 a while back. Hopefully someday it gets implemented.

@dgozman
Copy link

dgozman commented Oct 2, 2019

@weinand I'd like to revive this discussion. Multiple "heaps" or "execution contexts" in a single thread is a reality in JS world: iframes in web, vm module in Node.js. Here is my draft proposal, let me know what you think.

type GlobalScopeId = string;

interface GlobalScope {
  /**
   * Unique identifier for the global scope.
   */
  id: GlobalScopeId;

  /**
   * A name of the global scope.
   */
  name: string;
}

interface Thread {
  /**
   * All global scopes which belong to this thread. When global scopes change,
   * debug adapter should send a ThreadEvent with 'globalScopesChanged' reason.
   */
  globalScopes?: GlobalScope[];
}

interface EvaluateArguments {
  /**
   * Global scope in which to evaluate. Should be ignored if frameId is specified.
   * If not specified, default global scope should be used.
   */
  globalScopeId?: GlobalScopeId;
}

interface CompletionsArguments {
  /**
   * Global scope for which to return completions. Should be ignored if frameId is specified.
   * If not specified, default global scope should be used.
   */
  globalScopeId?: GlobalScopeId;
}

interface InitializeRequestArguments {
  /**
   * Client supports multiple global scopes per thread.
   */
  supportsMultipleGlobalScopes?: boolean;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Request for new features or functionality
Projects
None yet
Development

No branches or pull requests

6 participants