Recently, the company developed its own small program, and I was responsible for the debugging part, mainly for developer tools.

This article is a guide article.

Debugging capability has gone through 4 versions from 0 to 1, which will be introduced in the following articles.

The initial version

The diagram above shows a communication diagram before debugging existed.

At that time, the logic code was isolated from the rendering code, where the logic code was running in a VM.

  1. The render layer communicates with Electron via the IPC capability provided by Electron.
  2. Electron holds a reference to the VM, and upon receiving a request from the render layer, electron hands it directly to the VM for execution.
  3. The code running in the VM throws the execution result through the VM’s Context method.
  4. The VM receives the code directly and returns the result to the rendering layer via executeJavaScript via a reference to the rendering layer container BrowserView.

Through the above four steps, a simple communication loop of rendering layer and logic layer is completed. There are render layer code, logic layer code, Preload, electron, VM, BrowserView six roles involved.

This stage is characterized by the isolation of the rendering code from the logical code and the lack of basic breakpoint debugging capabilities.

The first edition

This version is a little more complex than the original version and implements breakpoint capability for logical code. Its main improvements are:

  1. Move the VM into a separate process.
  2. Run logic layer code in debug state with node –inspect-brk.
  3. Since the logical layer code runs in a separate process, IPC is used to maintain communication between the rendering layer and the logical layer.
  4. Added visual debugging interface. Basic debug control operations can be performed on the code, and log output from the rendering layer can be seen from the console.

The downside is that you can’t review DOM structures or view Network records.

The second edition

The improvement over the previous version is that you can view Network records, as well as review the basic DOM structure.

Run example:

This version runs the logical layer code inside the worker. The debug review panel uses the debugging tool delivered with ELECTRON.

Running the logical code inside the worker solves the problem of Network review. Electron comes with a debug tool that allows you to add a Tab to the debug tool at a fraction of the cost, using the capabilities of the Chrome Extensions.

In order not to affect the execution of the logical code, this version uses Adapter to play the role of the vm of the previous version, so that the upper-layer logical code is unaware of the migration of the runtime environment, and adapter is responsible for the communication of the underlying data.

The biggest difficulty in this version is the DOM structure review. This is because The Chrome Extensions run on the logical layer container and the DOM information is on the render layer container. One might ask, why not just put the extension on the render layer container? The answer is no, because the console Network source capabilities are strictly tied to the logical layer. There is only one debug panel, and cost trade-offs must be made.

The solution to DOM censorship is to open up the communication channel between the rendering layer and the logical layer. The implementation of Chrome Extensions has to be mentioned here, which consists of three main parts:

  • Frontend. Js is a file that runs in the debug panel TAB.
  • Backend.js this file runs in the context of a web page.
  • Background. js is the file responsible for communication between frontend. Js and backend.js.

The following diagram briefly describes the relationship between them:

This is illustrated by extensions Vue-devTools, which is already very mature. Backend. js is responsible for obtaining the vue component tree structure from the page and sending it to frontend.

In our applet, backend.js runs in an environment where there is no Vue component information. Where is this information? It is located in the runtime environment of the rendering layer. So we need to make some modifications (based on vue-DevTools) as follows:Backend. js is used to run in the logic layer web page environment to run in the render layer web page environment. Backend.js, which used to run in the logical layer web environment, becomes backend.proxy.js, which is responsible for communication between the internal and external environment. The inside here refers to proxy.js in extensions and the outside refers to electron.js. Gluelayout.js plays the role of proxy.js in the rendering layer and is responsible for communication adaptation between Backend.js and the outside world.

The above merely opens up the communication channel between the logic layer and the rendering layer, but this is not enough. Since we need to examine the DOM structure of the render layer, we can only see the Vue component structure of the render layer. So it’s going to take some work.

To accommodate both DOM and data review capabilities, we came up with an innovative way to combine the component structure with the DOM structure. Such as:

# Main.vue
<template>
  <div class="main">
    <Hello></Hello>
  </div>
</template>
Copy the code
# Hello.vue <template> <div class="hello"> <span>This is Hello components! </span> </div> </template>Copy the code

Actual review will change to:

<div class="main">
  <Hello>
    <div class="hello">
      <span>This is Hello components!</span>
    </div>
  </Hello>
</div>
Copy the code

When a component node is clicked, information about the component itself is displayed (a full vue-DevTools capability), whereas when a DOM node is clicked, information about the element itself is displayed (not implemented).

Compared with the previous version, this version realizes the review of DOM tree structure and component data, and also realizes the review of Network. The downside is that the Elements themselves don’t allow for basic capabilities like changing styles and looking at inside and outside margins.

The third edition

Compared with the previous edition, this edition has more complete capabilities:

  • Complete DOM review capabilities.
  • Console Console.
  • The Source debugging.
  • The Network is examined.
  • Page data review.

Compared to other applets developer tools on the market, it has all the basic capabilities.

The debug panel of this release uses the Chrome DevTools Frontend scheme used in the second release. The difference with the second version is that the logic code is run by the third version of the scheme.

There are three big challenges to this release:

  1. How do I use a debug panel to control the DOM structure of the render layer and the code logic of the logic layer?
  2. How to add a new fully capable TAB to the Chrome DevTools Frontend project without data?
  3. How to obtain audit data?

Here simply explain how to solve the above three problems respectively.

Question 1

Chrome DevTools Frontend (frontend for short) is an official Google debug panel project for Chrome.

When frontend is started, it connects to a target debug address via WebSocket. Note that ** can only be one address. ** So the question is, now that the logic layer and the render layer are running in two separate environments, who should I connect to? You can’t connect anyone.

The only solution is to provide a debug relay service and let frontend connect to the relay service. The relay service connects to the logical layer debug service and the render layer debug service respectively. As shown below:

Question 2

Since frontend project was completely changed to TS writing this year, it took more than 10 minutes to compile each revision and review. In order to compress this considerable time, we found the last version before we changed it to TS. This version was written in JS and can be previewed directly in the browser. The biggest benefit is that you can debug your code in real time, which opens the door to understanding how the Frontend project works.

This is not enough, because unlike traditional front-end projects, frontend projects do not have a build process and rely on dynamic loading of configuration files to generate large projects.

After some time of fumbling and a lot of debugging, we found the complete process of frontend from startup to final rendering of a TAB. Knowing how it loads, adding a TAB is a sure thing.

Add a TAB to frontend

But that’s where the problem ends, right? No, no, no, it’s still early. All you have to do is have a TAB, but it’s empty, there’s nothing in it, so how do you add something to it?

The following code is part of the initialization code for Element in the debug panel:

Emmm is, how to put it, completely different from what we’re used to. It’s neither native DOM manipulation nor third-party frameworks like JQuery or Vue.

The original frontend encapsulates a large number of components, and the UI.Panel.Panel inherited from ElementPanel in the code above is a component. I initially tried to use these components, but the lack of documentation and the sheer volume of code made it difficult and ineffective to use. After reading the code, I found the element exposed by these components, so I could mount the Vue to this element and implement the TAB content in a Vue manner. As shown in the figure:

Question 3

Frontend communicates with the outside world based on Websocket. The Element, Console, and Source modules exchange data with the outside world through the built-in WebSocket client. This Websocket instance is highly encapsulated and difficult to use directly in Vue. For example, Element gets DOM data in this way:

Note that the invoke_getDocument method here is synthesized dynamically:

I won’t go into the details here. In short, it is very difficult to transform the communication process in the way of frontend. At this time, I wanted to break new ground and build a communication channel by myself. But later thought and gave up, this is not a good way. We decided to start with the built-in Websocket to see which key areas could be exposed for global use. Finally, after continuous debugging, we found this key object:

This way, I can use it as I like:

export function sendMessage(method, params) {
    return new Promise((resolve, reject) = > {
        // self.target is the communication key object
        self.target._router.sendMessage(""."DataInspect".`DataInspect.${method}`, params, (error, result) = > {
            if (error) {
                console.error('Request ' + method + ' failed. ' + JSON.stringify(error));
                reject(null);
                return; } resolve(result); })})}Copy the code

Target is an object that guarantees a perfect one-to-one correspondence between a request and a callback, without error, confusion, or confusion. This gave me the idea to actively listen for logical layer callbacks later.

Final

* Small program debugging technology from 0 to 1 has experienced a total of three versions of the evolution to reach a perfect state, although the process of evolution is constantly overthrowing the previous scheme, but the result is perfect after all. This is an inevitable process, because you don’t know the existence of pits until you step on them.

The biggest challenge for me in debugging small programs is that every step is almost exploratory. Assume, implement, verify the infinite loop, constantly improve.


Finally post a third version of the basic debugging ability to achieve the full picture:

Data Review Panel:

The Source panel:

Console Console:

DOM Review Panel:

Debug interrupt:

Network Network resource review:

Network XHR review:

All right, that’s all for the introduction. The full implementation of version 3 will be detailed in several articles that follow.