Skip to content

VS Code: Process Architecture #34

@xwcoder

Description

@xwcoder

VS Code: Process Architecture

The work of enabling process sandbox is started at early 2020, and plan to finish at the beginning of 2023.

Process Architecture before enabling the sandbox

IMAGE

  • Renderer process is a major client of Node.js APIs - for example to read and write files.

  • Most processes are Node.js child processes (in green) forked from the renderer process.

  • Most of the IPC is implemented via Node.js sockets.

  • The Shared Process is a hidden Electron window with Node.js enabled. All other windows can communicate with to perform complex tasks such as extension installation. It is a work process.

    const worker = new BrowserWindow({
        show: false,
        webPreferences: { nodeIntegration: true }
      })
     await worker.loadFile('worker.html')
    • The disadvantage is that Node.js socket communication is not possible in sandboxed renderers.
  • The extension host is a process that runs all the installed extensions isolated from the renderer process. There is on extension host per opened window.

Process Architecture after enabling the sandbox

This is the process architecture in late 2022.

IMAGE

  • Remove all Node.js dependencies from the renderer

    • Code that was previously running in one process to be split and run across multiple processes.
    • Replace global objects such as Node.js Buffer with compatiable variants such as Uint8Array.
  • Expose a vscode global variable for communication using preload scripts

    // src/vs/base/parts/sandbox/electron-sandbox/preload.js
    
    if (process.contextIsolated) {
      try {
        contextBridge.exposeInMainWorld('vscode', globals);
      } catch (error) {
        console.error(error);
      }
    } else {
      // @ts-ignore
      window.vscode = globals;
    }
  • Moving child processes out of the renderer

    • Converted the extension host process to be a utility process that is created from the main process.
    • Converted the shared process to be a utility process
      • Notice: At the time the diagram was created, shared process was a hidden window, now it is a utility process. So, I don’t confirm this part in the diagram is correct. I will review it after reading source code of this part.
    • Integrated terminals and file watching moved to be child processes of the shared process.
  • Fast IPC via message port

    • Renderer processes directly communicate with shared process and extension host process using MessagePort.

    • The Sequence of passing MessagePort across shared process and renderer process

      IMAGE

      • The shared process creates the message ports P1 and P2 and keeps P1.
      • P2 is sent via Electron IPC to the main process.
      • The main process forwards P2 to the requesting renderer process.
      • P2 ends up in the preload script of that renderer process.
      • The preload script forwards P2 into the renderer main script.
      • The main script receives P2 and can use it to send messages directly.

Other efforts

Change the origin of the renderer

  • Abandon the file protocol.

    // vs/platform/protocol/electron-main/protocolMainService.ts
    
    // Block any file:// access
    defaultSession.protocol.interceptFileProtocol('file', (request, callback) => this.handleFileRequest(request, callback));
    
    ...
    
    private handleFileRequest(request: Electron.ProtocolRequest, callback: ProtocolCallback) {
    		const uri = URI.parse(request.url);
    
    		this.logService.error(`Refused to load resource ${uri.fsPath} from ${Schemas.file}: protocol (original URL: ${request.url})`);
    
    		return callback({ error: -3 /* ABORTED */ });
    	}
  • Register the vscode-file protocol configured to behave like HTTPS.

    // src/main.js
    const { protocol } = require('electron');
    
    protocol.registerSchemesAsPrivileged([
    	{
    		scheme: 'vscode-webview',
    		privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, allowServiceWorkers: true, }
    	},
    	{
    		scheme: 'vscode-file',
    		privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true }
    	}
    ]);
    
    // vs/platform/protocol/electron-main/protocolMainService.ts
    
    // Register vscode-file:// handler
    defaultSession.protocol.registerFileProtocol('vscode-file', (request, callback) => this.handleResourceRequest(request, callback));

Replace the Electron webview element with iframe

Enable renderer process reuse

  • Traditionally the renderer process would terminate and restart every time a navigation occurs to another URL.
  • Sandboxed renderer processes are kept alive, even when navigating URLs.

Adapting AMD code loader. (WIP)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions