Skip to content
PalmerAL edited this page Aug 17, 2024 · 5 revisions

Min generally follows the standard architecture of an Electron application. In Min, this means that there is a renderer process for each window that runs the app's UI (the "UI process"), and also a separate renderer process for each webpage loaded in a tab. The following diagram shows what this would look like, assuming there are two browser windows open, one of which has two open tabs:

Min architecture diagram

  • The main process is responsible for opening windows, creating the app menu, and handling communication between the other processes. It is also responsible for filtering network requests (if content blocking is enabled), and forwarding notifications for downloaded files and permission prompts to the UI process.
    • Code that runs in this process is mostly located in the main directory.
    • This process has access to NodeJS APIs, but generally does not display UI elements.
  • A UI process is a renderer process that displays the app UI for a window - including the tab bar, task overlay, download manager, etc.
    • Like the main process, the UI process has full access to NodeJS APIs. This means that it can launch subprocesses, read local files, etc, but it shouldn't be used to display untrusted content.
    • This process loads the 'index.html' file located at the root of the repository, which in turn loads runs most of the code in the js/ directory.
  • The places window is a hidden window that launches from the main process. It opens a connection to the places database (containing history and bookmarks),and performs operations like search and getting bookmark tag suggestions. It communicates over IPC with each UI process.
  • Each tab runs in its own process inside a BrowserView. These processes are sandboxed, so they don't have access to the Node API.
    • Each time a new page is loaded in one of these processes, a preload script is also loaded. This script also runs within the sandbox, but it has access to Electron's ipc module, allowing it to send data back to the main process. Examples of what the preload script does include:
      • Extracting the text content of the page to use for full-text search
      • Analyzing the page content to determine if it can be opened in reader mode
      • Detecting password inputs on the page that can be autofilled.
      • The code for this script is located in the js/preload/ directory.

All of these processes communicate with each other using the Electron IPC module.

In many cases, the tab and UI processes need to send data to each other - for example, typing a new URL in the searchbar (of the UI process) should load that page in the tab process, and when the page loads, it should send its title to the UI process to be displayed. However, the UI process and tabs don't communicate directly with each other - instead, they send messages to the main process, which then forwards them to their destination.

Process entry points

The main process and preload script are all made up of a series of scripts that are concatenated together, so you can reference the contents of any script from any other. The list of files that they include are defined in scripts/buildMain.js and scripts/buildPreload.js.

The UI process is gradually being refactored into modules that can be imported. This means that in some cases, you can reference a function that you wish to use directly, but in other cases, it will need to be imported. When the UI process starts, it loads index.html; this loads dist/bundle.js, which is compiled from js/default.js.

Useful modules

Tabs/Tasks

The tab and task objects, defined in tab.js and task.js, store all information related to the user's current session. They also allow attaching listeners to react to events (such as when a tab's URL changes). These objects are synced across windows (see "multi-window architecture").

Localization system (Main, UI, content)

  • The strings for each language are located in the localization/languages/ directory. If you add a new string, you'll need to add it to each language file (find and replace is useful for this). For languages where you don't know the translation, you can set the value to null.
  • Running npm run buildLocalization generates a dist/localization.build.js script that can be loaded in any page.
    • If you're creating a new page, you'll need to load this script.
  • Once this script is loaded, you can call l(stringName) anywhere to get a localized string in the user's current language.
  • There are also some special attributes you can set on any HTML element to automatically insert strings inside HTML files (however, these will only evaluated once when the document is loaded);
    • data-string="stringName" sets the element's text content
    • data-label="stringName" sets the element's title attribute
    • data-value="stringName" sets the element's value attribute
  • Sometimes it's necessary to include HTML inside a translated string. To create a string with HTML, set the value to {unsafeHTML: <value>}. Then use one of the following methods to insert the string:
    • From a script: l(stringName).unsafeHTML
    • From HTML: add the data-allowHTML attribute to the element, then use one of the attributes above.

Settings (Main, UI, Content)

  • settings.get(name) returns the setting's current value.
  • settings.set(name, value) updates the setting.
  • Calling settings.listen(name, cb) will call cb(value) once after the settings are loaded, and again whenever the value of the setting is changed. In most cases, this should be used instead of settings.get so that setting changes can take effect without requiring a restart.

In-content pages (such as the settings page itself) use a different implementation of this (settingsContent.js) that transfers settings data over IPC, but the API is the same.

Webviews (UI)

  • Responsible for managing communication between the main process (viewManager.js) and currently-open BrowserViews (one per tab).
  • Each view is just a webContents, so you can use all the methods and events described in the Electron documentation.
    • To call a method on a tab, use webviews.callAsync(tabId, methodName, arguments, callback)
    • To attach an event listener to all views, use webviews.bindEvent(event, callback, options)
    • To listen for an IPC message from a view, use webviews.bindIPC(name, callback)
  • BrowserViews appear on top of all other UI elements, so if you want to display content on top of a tab, you'll need to either create a popup window or call webviews.requestPlaceholder(name). Doing this will hide the current tab and replace it with a blurred preview image that other content can appear on top of. Calling webviews.hidePlaceholder(name) will show the tab again as soon as long as no other code is still requesting a placeholder.

BrowserUI (UI)

  • Handles common UI actions, such as creating or closing tabs, and calls the appropriate functions in webviews, tabBar, and tabState.

Multi-window architecture

  • Each window runs in its own isolated process.
  • A task can be assigned to a single window at a time, through its selectedInWindow property.
  • Open tabs are a set of BrowserView instances held in the main process. This means that when a tab is transferred between windows, the tab state moves automatically (ie the page doesn't reload).
  • When a tab or task's data object is modified in one window, windowSync.js copies those changes to all other open windows. This means that the global tasks object is the same no matter which window you access it from, but there may be a small delay in replicating changes to it.