674 changes: 0 additions & 674 deletions LICENSE

This file was deleted.

101 changes: 72 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,75 +1,118 @@
<p align="center">
<a href="https://wexond.net"><img src="static/app-icons/icon.png" width="256"></a>
<a href="https://wexond.net"><img src="static/icons/icon.png" width="256"></a>
</p>

<div align="center">
<h1>Wexond</h1>
<h1>Wexond Browser Base</h1>

[![Actions Status](https://github.com/wexond/desktop/workflows/Build/badge.svg)](https://github.com/wexond/desktop/actions)
[![Downloads](https://img.shields.io/github/downloads/wexond/desktop/total.svg?style=flat-square)](https://github.com/wexond/desktop/releases)
[![Downloads](https://img.shields.io/github/downloads/wexond/desktop/total.svg?style=flat-square)](https://wexond.net)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwexond%2Fwexond.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwexond%2Fwexond?ref=badge_shield)
[![PayPal](https://img.shields.io/badge/PayPal-Donate-brightgreen?style=flat-square)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=VCPPFUAL4R6M6&source=url)
[![Discord](https://discordapp.com/api/guilds/307605794680209409/widget.png?style=shield)](https://discord.gg/P7Vn4VX)

Wexond is an extensible and privacy-focused web browser with a totally different user experience, built on top of `Electron` and `React`. It aims to be fast, private, beautiful, extensible and functional.
Wexond Base is a modern web browser, built on top of modern web technologies such as `Electron` and `React`, that can also be used as a framework to create a custom web browser (see the [License](#license) section).

</div>

# Table of Contents:
- [Motivation](#motivation)
- [Features](#features)
- [Screenshots](#screenshots)
- [Downloads](#downloads)
- [Contributing](#contributing)
- [Development](#development)
- [Running](#running)
- [Documentation](#documentation)
- [License](#license)

# Motivation

Compiling and editing Chromium directly may be challenging and time consuming, so we decided to build Wexond with modern web technologies. Hence, the development effort and time is greatly reduced. Either way Firefox is based on Web Components and Chrome implements new dialogs in WebUI (which essentially is hosted in WebContents).

# Features

- **Wexond Shield** - Browse the web without any ads and don't let websites to track you. Thanks to the Wexond Shield, websites can load even 2 times faster!
- **Built-in dark mode** - Wexond has a built-in dark theme, but it also changes white websites to dark with just a one click in Overlay. No eyestrain at night anymore!
- **Beautiful and minimalistic UI** - The address bar is hidden to take less space, but it doesn't impact on usability in any way. It's even better! [Give it a shot](https://wexond.net)
- **Tab groups** - Easily group tabs to groups and access them really fast.
- **Partial support for Chrome extensions** - Install some extensions from Chrome Web Store* (see [#110](https://github.com/wexond/wexond/issues/110))
- **Packages** - Extend Wexond for your needs, by installing or developing your own packages* ([#147](https://github.com/wexond/wexond/issues/147)) (WIP)
- **Wexond Shield** - Browse the web without any ads and don't let websites to track you. Thanks to the Wexond Shield powered by [Cliqz](https://github.com/cliqz-oss/adblocker), websites can load even 8 times faster!
- **Chromium without Google services and low resources usage** - Since Wexond uses Electron under the hood which is based on only several and the most important Chromium components, it's not bloated with redundant Google tracking services and others.
- **Fast and fluent UI** - The animations are really smooth and their timings are perfectly balanced.
- **Highly customizable new tab page** - Customize almost an every aspect of the new tab page!
- **Customizable browser UI** - Choose whether Wexond should have compact or normal UI.
- **Tab groups** - Easily group tabs, so it's hard to get lost.
- **Scrollable tabs**
- **Partial support for Chrome extensions** - Install some extensions directly from Chrome Web Store\* (see [#110](https://github.com/wexond/wexond/issues/110)) (WIP)

## Other basic features

- Downloads popup with currently downloaded items (download manager WebUI page is WIP)
- History manager
- Bookmarks bar & manager
- Settings
- Find in page
- Dark and light theme
- Omnibox with autocomplete algorithm similar to Chromium
- State of the art tab system

# Screenshots

![image](https://user-images.githubusercontent.com/11065386/70392294-ed20b800-19de-11ea-942b-216e9916f07c.png)
![image](https://user-images.githubusercontent.com/11065386/81024159-d9388f80-8e72-11ea-85e7-6c30e3b66554.png)

UI normal variant:
![image](https://user-images.githubusercontent.com/11065386/81024186-f40b0400-8e72-11ea-976e-cd1ca1b43ad8.png)

![image](https://user-images.githubusercontent.com/11065386/70392310-180b0c00-19df-11ea-98de-ba96aece86c4.png)
UI compact variant:
![image](https://user-images.githubusercontent.com/11065386/81024222-13099600-8e73-11ea-9fc9-3c63a034403d.png)
![image](https://user-images.githubusercontent.com/11065386/81024252-2ddc0a80-8e73-11ea-9f2f-6c9a4a175c60.png)

# Downloads
- [Stable and beta versions](https://github.com/wexond/desktop/releases)
- [Nightlies](https://github.com/wexond/desktop-nightly/releases)

# [Roadmap](https://github.com/wexond/wexond/projects)

# Contributing

If you have found any bugs or just want to see some new features in Wexond, feel free to open an issue. We're open to any suggestions. Bug reports would be really helpful for us and appreciated very much. Wexond is in heavy development and some bugs may occur. Also, please don't hesitate to open a pull request. This is really important to us and for the further development of this project.
If you have found any bugs or just want to see some new features in Wexond, feel free to open an issue. Every suggestion is very valuable for us, as they help us improve the browsing experience. Also, please don't hesitate to open a pull request. This is really important to us and for the further development of this project.

By opening a pull request, you agree to the conditions of the [Contributor License Agreement](cla.md).

# Development

## Running

Before running Wexond, please ensure you have **latest** [`Node.js`](https://nodejs.org/en/) installed on your machine.
Before running Wexond, please ensure you have **latest** [`Node.js`](https://nodejs.org/en/) and [`Yarn`](https://classic.yarnpkg.com/en/docs/install/#windows-stable) installed on your machine.

When running on Windows, make sure you have build tools installed. You can install them by running this command as **administrator**:
### Windows

Make sure you have build tools installed. You can install them by running this command as **administrator**:

```bash
$ npm i -g windows-build-tools
```

Firstly, run this command to install all needed dependencies. If you have encountered any problems, please report it.

```bash
$ npm install
$ yarn # Install needed depedencies.
$ yarn rebuild # Rebuild native modules using Electron headers.
$ yarn dev # Run Wexond in development mode
```

The given command below will run Wexond in the development mode.
### More commands

```bash
$ npm run dev
$ yarn compile-win32 # Package Wexond for Windows
$ yarn compile-linux # Package Wexond for Linux
$ yarn compile-darwin # Package Wexond for macOS
$ yarn lint # Runs linter
$ yarn lint-fix # Runs linter and automatically applies fixes
```

More commands can be found in [`package.json`](package.json).

# Documentation

Guides and the API reference are located in [`docs`](docs) directory.

### Sponsors

[![Sponsors](https://opencollective.com/wexond/tiers/sponsor.svg?avatarHeight=48)](https://opencollective.com/wexond)

### Backers

[![Backers](https://opencollective.com/wexond/tiers/backer.svg?avatarHeight=48)](https://opencollective.com/wexond)
# License

## License
Usage of this project code and assets is disallowed.

[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fwexond%2Fwexond.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fwexond%2Fwexond?ref=badge_large)
By sending a Pull Request, you agree that your code may be relicensed or sublicensed.
35 changes: 35 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Development

## IPC

Now, the preferred way to communicate between processes is to use [`@wexond/rpc-electron`](https://github.com/wexond/rpc) package.

Example:

Handling the IPC message in the main process:

[`src/main/network/network-service-handler.ts`](../src/main/network/network-service-handler.ts)


Sending the IPC message to the main process:

```ts
const { data } = await networkMainChannel.getInvoker().request('http://localhost');
```

Common RPC interface

[`src/common/rpc/network.ts`](../src/common/rpc/network.ts)

## Remote module

As Electron will be deprecating the `remote` module, we are migrating to our RPC solution.

## Node integration

We are going to turn off `nodeIntegration`, enable `contextIsolation` and `sandbox` in the UI webContents,
therefore we prefer not having requires to node.js built-in modules in renderers.

## Project structure

Common interfaces, constants etc. should land into the `common` directory.
97 changes: 72 additions & 25 deletions docs/keyboard-shortcuts.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,78 @@

## Windows and Linux

### Shortcuts regarding tabs and windows
### Tab and window shortcuts

| Action | Shortcut |
| --------------------------- | -------------------------------- |
| Open a new window | `Ctrl + N` |
| Open a new incognito window | `Ctrl + Shift + N` |
| Open a new tab | `Ctrl + T` |
| Select next tab | `Ctrl + Tab` |
| Select previous tab | `Ctrl + Shift + Tab` |
| Select specific tab | `Ctrl + 1` to `Ctrl + 8` |
| Select the rightmost tab | `Ctrl + 9` |
| Go back | `Alt + Left Arrow` |
| Go forward | `Alt + Right Arrow` |
| Close selected tab | `Ctrl + W` or `Ctrl + F4` |
| Close current window | `Ctrl + Shift + W` or `Alt + F4` |
| Close Wexond | `Ctrl + Shift + Q` |

### Wexond feature shortcuts

| Action | Shortcut |
| --------------------------------------- | ------------------------------- |
| Focus the address bar | `Ctrl + L` or `Alt + D` or `F6` |
| Open the menu | `Alt + F` or `Alt + E` |
| Open find in page dialog | `Ctrl + F` |
| Open the Bookmarks Manager in a new tab | `Ctrl + Shift + O` |
| Open the History page in a new tab | `Ctrl + H` |

### Webpage shortcuts

| Action | Shortcut |
| -------------------------------- | ---------------------------------- |
| Reload | `F5` or `Ctrl + R` |
| Reload ignoring cache | `Shift + F5` or `Ctrl + Shift + R` |
| Go to next clickable element | `Tab` |
| Go to previous clickable element | `Shift + Tab` |
| Add current website to bookmarks | `Ctrl + D` |

## macOS

### Tab and window shortcuts

| Action | Shortcut |
| --------------------------- | ---------------------------- |
| Open a new window | Ctrl + N |
| Open a new incognito window | Ctrl + Shift + N |
| Open a new tab | Ctrl + T |
| Select next tab | Ctrl + Tab |
| Go back | Alt + Left Arrow |
| Go forward | Alt + Right Arrow |
| Close selected tab | Ctrl + W or Ctrl + F4 |
| Close current window | Ctrl + Shift + W or Alt + F4 |
| Close Wexond | Ctrl + Shift + Q |

### Shortcuts for Wexond functions

| Action | Shortcut |
| ------------------------ | ------------------------------ |
| Open Overlay | Alt + F or Alt + E or Ctrl + L |
| Open find in page dialog | Ctrl + F |

### Shortcuts regarding websites

| Action | Shortcut |
| -------------------------------- | -------------- |
| Reload | F5 or Ctrl + R |
| Go to next clickable element | Tab |
| Go to previous clickable element | Shift + Tab |
| Open a new window | `⌘ + N` |
| Open a new incognito window | `⌘ + Shift + N` |
| Open a new tab | `⌘ + T` |
| Select next tab | `⌘ + Tab` |
| Select previous tab | `⌘ + Shift + Tab` |
| Select specific tab | `⌘ + 1` to `⌘ + 8` |
| Select the rightmost tab | `⌘ + 9` |
| Go back | `⌘ + [` or `⌘ + Left Arrow` |
| Go forward | `⌘ + ]` or `⌘ + Right Arrow` |
| Close selected tab | `⌘ + W` |
| Close current window | `⌘ + Shift + W` |
| Hide Wexond | `⌘ + H` |
| Close Wexond | `⌘ + Q` |

### Wexond feature shortcuts

| Action | Shortcut |
| --------------------------------------- | ---------------- |
| Focus the address bar | `⌘ + L` |
| Open find in page dialog | `⌘ + F` |
| Open the Bookmarks Manager in a new tab | `⌘ + Option + B` |
| Open the History page in a new tab | `⌘ + Y` |

### Webpage shortcuts

| Action | Shortcut |
| -------------------------------- | --------------- |
| Reload | `⌘ + R` |
| Reload ignoring cache | `⌘ + Shift + R` |
| Go to next clickable element | `Tab` |
| Go to previous clickable element | `Shift + Tab` |
| Add current website to bookmarks | `⌘ + D` |
8 changes: 5 additions & 3 deletions electron-builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
"nsis": {
"include": "static/installer.nsh"
},
"electronVersion": "13.1.4",
"generateUpdatesFilesForAllChannels": true,
"asar": true,
"directories": {
"output": "dist",
"buildResources": "static/app-icons"
"buildResources": "static/icons"
},
"files": ["build/**/*", "package.json", "static/**/*"],
"publish": "github",
Expand All @@ -16,11 +18,11 @@
"target": [
{
"target": "AppImage",
"arch": ["ia32", "x64"]
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["ia32", "x64"]
"arch": ["x64"]
}
]
},
Expand Down
143 changes: 74 additions & 69 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "wexond",
"version": "4.0.0-beta.3",
"version": "6.0.0-nightly.2",
"sideEffects": false,
"description": "Extensible, fast and innovative web browser with material UI.",
"keywords": [
"web-browser",
Expand All @@ -21,82 +22,86 @@
"url": "https://github.com/wexond/wexond/issues"
},
"scripts": {
"dev-renderer": "cross-env DEV=1 webpack serve --config webpack.config.renderer.js",
"dev-webpack": "cross-env DEV=1 webpack",
"build-renderer": "webpack --config webpack.config.renderer.js",
"dev": "cross-env START=1 npm run watch",
"build": "npm run extensions && concurrently \"webpack --config webpack.config.renderer.js\" \"webpack\" \"webpack --config webpack.config.web.js\"",
"start": "cross-env NODE_ENV='dev' electron .",
"watch": "npm run extensions && concurrently \"cross-env ENV='dev' webpack-dev-server --config webpack.config.renderer.js\" \" cross-env ENV='dev' webpack\" \"cross-env ENV='dev' webpack-dev-server --config webpack.config.web.js\"",
"compile-win32": "npm run build && electron-builder -w -p always",
"compile-darwin": "npm run build && electron-builder -m -p always",
"compile-linux": "npm run build && electron-builder -l -p always",
"compile": "node scripts/compile.js",
"build": "rimraf build && concurrently \"npm run build-renderer\" \"webpack\"",
"ci-build": "node scripts/ci-build.js",
"start": "electron .",
"watch": "concurrently \"npm run dev-renderer\" \"npm run dev-webpack\"",
"compile-win32": "npm run build && electron-builder -w",
"compile-darwin": "npm run build && electron-builder -m",
"compile-linux": "npm run build && electron-builder -l",
"lint": "eslint \"src/**/*.ts*\" \"src/**/*.tsx*\"",
"lint-fix": "npm run lint -- --fix",
"postinstall": "electron-builder install-app-deps",
"extensions": "node scripts/extensions.js"
"rebuild": "electron-builder install-app-deps"
},
"devDependencies": {
"@cliqz/adblocker-electron": "1.8.6",
"@types/chrome": "0.0.95",
"@types/crypto-js": "^3.1.43",
"@types/extract-zip": "^1.6.2",
"@types/gsap": "1.20.2",
"@types/jszip": "^3.1.7",
"@types/nedb": "1.8.9",
"@types/node": "13.7.1",
"@types/node-fetch": "^2.5.4",
"@types/react": "16.9.19",
"@types/react-dom": "16.9.5",
"@types/styled-components": "4.4.3",
"@typescript-eslint/eslint-plugin": "^2.19.2",
"@typescript-eslint/parser": "^2.19.2",
"@babel/core": "^7.14.6",
"@cliqz/adblocker-electron": "1.22.2",
"@electron/remote": "^1.2.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@types/animejs": "^3.1.3",
"@types/chrome": "0.0.145",
"@types/crypto-js": "^4.0.1",
"@types/jszip": "^3.4.1",
"@types/nedb": "1.8.11",
"@types/node": "15.12.5",
"@types/node-fetch": "^2.5.10",
"@types/react": "17.0.11",
"@types/react-dom": "17.0.8",
"@types/rimraf": "^3.0.0",
"@types/styled-components": "5.1.10",
"@typescript-eslint/eslint-plugin": "^4.28.0",
"@typescript-eslint/parser": "^4.28.0",
"@wexond/rpc-core": "^1.0.3",
"@wexond/rpc-electron": "^1.0.3",
"animejs": "^3.2.1",
"awesome-node-loader": "^1.1.1",
"axios": "0.19.2",
"cache-loader": "^4.1.0",
"concurrently": "^5.1.0",
"copy-webpack-plugin": "^5.1.1",
"cross-env": "7.0.0",
"crypto-js": "^4.0.0",
"electron": "8.0.0",
"electron-builder": "21.2.0",
"electron-extensions": "^6.0.1",
"electron-updater": "4.2.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.18.3",
"extract-zip": "^1.6.7",
"file-loader": "^5.0.2",
"file-type": "14.1.2",
"fork-ts-checker-webpack-plugin": "^4.0.4",
"gsap": "^3.1.1",
"hard-source-webpack-plugin": "^0.13.1",
"html-webpack-plugin": "^3.2.0",
"icojs": "^0.14.0",
"jszip": "^3.2.2",
"mobx": "5.15.4",
"mobx-react-lite": "1.5.2",
"babel-loader": "^8.2.2",
"concurrently": "^6.2.0",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "7.0.3",
"electron": "13.1.4",
"electron-builder": "22.11.7",
"electron-extensions": "^7.0.0-beta.3",
"electron-updater": "4.3.9",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"file-loader": "^6.2.0",
"file-type": "16.5.0",
"fork-ts-checker-webpack-plugin": "^6.2.12",
"html-webpack-plugin": "^5.3.2",
"icojs": "^0.16.1",
"jszip": "^3.6.0",
"mobx": "6.3.2",
"mobx-react-lite": "3.2.0",
"nedb": "1.8.0",
"node-bookmarks-parser": "^2.0.0",
"node-fetch": "^2.6.0",
"node-vibrant": "^3.1.5",
"prettier": "1.19.1",
"pretty-bytes": "5.3.0",
"react": "16.12.0",
"react-dom": "16.12.0",
"react-hot-loader": "4.12.19",
"node-fetch": "^2.6.1",
"prettier": "2.3.2",
"pretty-bytes": "5.6.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-refresh": "^0.10.0",
"react-windows-controls": "1.1.1",
"styled-components": "^5.0.1",
"terser": "^4.6.3",
"ts-loader": "^6.2.1",
"typescript": "^3.7.5",
"typescript-plugin-styled-components": "^1.4.4",
"webpack": "4.41.6",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "3.3.11",
"webpack-dev-server": "^3.10.3",
"webpack-merge": "^4.2.2"
"rimraf": "^3.0.2",
"source-map-support": "^0.5.19",
"styled-components": "^5.3.0",
"terser": "^5.7.0",
"terser-webpack-plugin": "^5.1.4",
"ts-loader": "^9.2.3",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"typescript": "^4.3.4",
"typescript-plugin-styled-components": "^1.6.0",
"webpack": "5.40.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "4.7.2",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.8.0"
},
"dependencies": {
"keytar": "^5.2.0"
}
"dependencies": {}
}
90 changes: 90 additions & 0 deletions scripts/ci-build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const package = require('../package.json');
const electronBuilder = require('../electron-builder.json');
const { promises } = require('fs');
const { resolve } = require('path');
const { run } = require('./utils');

const isNightly = package.version.indexOf('nightly') !== -1;

const getPlatform = () => {
if (process.platform === 'win32') return 'windows';
else if (process.platform === 'darwin') return 'mac';
return process.platform;
};

const getEnv = (name) => process.env[name.toUpperCase()] || null;

const setEnv = (name, value) => {
if (value) {
process.env[name.toUpperCase()] = value.toString();
}
};

const getInput = (name) => {
return getEnv(`INPUT_${name}`);
};

(async () => {
try {
if (isNightly) {
await promises.copyFile(
resolve(__dirname, '../package.json'),
resolve(__dirname, '../temp-package.json'),
);
await promises.copyFile(
resolve(__dirname, '../electron-builder.json'),
resolve(__dirname, '../temp-electron-builder.json'),
);
const newPkg = {
...package,
name: 'wexond-nightly',
repository: {
type: 'git',
url: 'git+https://github.com/wexond/desktop-nightly.git',
},
};
await promises.writeFile(
resolve(__dirname, '../package.json'),
JSON.stringify(newPkg),
);

const newEBConfig = {
...electronBuilder,
appId: 'org.wexond.wexond-nightly',
productName: 'Wexond Nightly',
directories: {
output: 'dist',
buildResources: 'static/nightly-icons',
},
};

await promises.writeFile(
resolve(__dirname, '../electron-builder.json'),
JSON.stringify(newEBConfig),
);
}

const release =
(getEnv('release') === 'true' || getEnv('release') === true) &&
getEnv('GH_TOKEN');
const platform = getPlatform();

if (platform === 'mac') {
setEnv('CSC_LINK', getEnv('mac_certs'));
setEnv('CSC_KEY_PASSWORD', getEnv('mac_certs_password'));
} else if (platform === 'windows') {
setEnv('CSC_LINK', getEnv('windows_certs'));
setEnv('CSC_KEY_PASSWORD', getEnv('windows_certs_password'));
}

run('yarn run build');
run(
`npx --no-install electron-builder --${platform} ${
release ? '-p always' : ''
}`,
);
} catch (e) {
console.error(e);
process.exit(1);
}
})();
14 changes: 0 additions & 14 deletions scripts/compile.js

This file was deleted.

42 changes: 0 additions & 42 deletions scripts/extensions.js

This file was deleted.

14 changes: 14 additions & 0 deletions scripts/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { execSync } = require('child_process');
const { resolve } = require('path');

const run = cmd => {
execSync(cmd, {
encoding: 'utf8',
cwd: resolve(__dirname, '..'),
stdio: 'inherit',
});
};

module.exports = {
run,
};
11 changes: 11 additions & 0 deletions src/common/renderer-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { configure } from 'mobx';
import { setIpcRenderer } from '@wexond/rpc-electron';
import { ipcRenderer } from 'electron';

export const configureUI = () => {
configure({ enforceActions: 'never' });
};

export const configureRenderer = () => {
setIpcRenderer(ipcRenderer);
};
10 changes: 10 additions & 0 deletions src/common/rpc/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RendererToMainChannel } from '@wexond/rpc-electron';

export interface ExtensionMainService {
uninstall(id: string): void;
inspectBackgroundPage(id: string): void;
}

export const extensionMainChannel = new RendererToMainChannel<ExtensionMainService>(
'ExtensionMainService',
);
14 changes: 14 additions & 0 deletions src/common/rpc/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RendererToMainChannel } from '@wexond/rpc-electron';

export interface ResponseDetails {
statusCode: number;
data: string;
}

export interface NetworkService {
request(url: string): Promise<ResponseDetails>;
}

export const networkMainChannel = new RendererToMainChannel<NetworkService>(
'NetworkService',
);
4 changes: 4 additions & 0 deletions src/common/webui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { WEBUI_BASE_URL, WEBUI_URL_SUFFIX } from '~/constants/files';

export const getWebUIURL = (hostname: string) =>
`${WEBUI_BASE_URL}${hostname}${WEBUI_URL_SUFFIX}`;
26 changes: 25 additions & 1 deletion src/constants/design.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
export const DEFAULT_TAB_MARGIN_TOP = 4;
export const COMPACT_TAB_MARGIN_TOP = 3;

export const DEFAULT_TAB_HEIGHT = 32;
export const COMPACT_TAB_HEIGHT = 32;

// Toolbar
export const TOOLBAR_HEIGHT = 38;
export const TOOLBAR_HEIGHT = 42;

export const TOOLBAR_BUTTON_WIDTH = 36;
export const TOOLBAR_BUTTON_HEIGHT = 32;

export const ADD_TAB_BUTTON_WIDTH = 28;
export const ADD_TAB_BUTTON_HEIGHT = 28;

export const DEFAULT_TITLEBAR_HEIGHT =
DEFAULT_TAB_MARGIN_TOP + DEFAULT_TAB_HEIGHT;
export const COMPACT_TITLEBAR_HEIGHT =
2 * COMPACT_TAB_MARGIN_TOP + COMPACT_TAB_HEIGHT;

export const VIEW_Y_OFFSET = TOOLBAR_HEIGHT + DEFAULT_TITLEBAR_HEIGHT;

// Widths
export const WINDOWS_BUTTON_WIDTH = 45;
export const MENU_WIDTH = 330;

// Dialogs
export const DIALOG_MIN_HEIGHT = 130;
export const DIALOG_MARGIN = 16;
export const DIALOG_TOP = 34;
export const DIALOG_MARGIN_TOP = 3;
6 changes: 5 additions & 1 deletion src/constants/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ export const DIRECTORIES = ['adblock', 'extensions', 'storage'];

export const WEBUI_PROTOCOL = 'wexond';

export const ERROR_PROTOCOL = 'wexond-error';

export const NETWORK_ERROR_HOST = 'network-error';

export const WEBUI_BASE_URL =
process.env.NODE_ENV === 'development'
? 'http://localhost:4445/'
? 'http://localhost:4444/'
: `${WEBUI_PROTOCOL}://`;

export const WEBUI_URL_SUFFIX = WEBUI_BASE_URL.startsWith('http')
Expand Down
74 changes: 40 additions & 34 deletions src/constants/settings.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,79 @@
import { ISettings } from '~/interfaces';
import { remote, app } from 'electron';

const pkg = require('../../package.json');

export const DEFAULT_SETTINGS: ISettings = {
theme: 'wexond-light',
darkContents: false,
shield: true,
multrin: true,
animations: true,
bookmarksBar: false,
suggestions: true,
themeAuto: true,
searchEngines: [],
searchEngine: 0,
startupBehavior: {
type: 'empty',
},
warnOnQuit: true,
version: pkg.version,
downloadsDialog: false,
downloadsPath: remote
? remote.app.getPath('downloads')
: app
? app.getPath('downloads')
: '',
};

export const DEFAULT_SEARCH_ENGINES = [
{
name: 'DuckDuckGo',
url: 'https://duckduckgo.com/?q=%s',
keywordsUrl: '',
keyword: 'duckduckgo.com',
icon:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACT1BMVEUAAAD/gFXfWjbeWTPfWDTeWDTeWDPfYDXfWDPeWTT/bUngWDPfWTPfWDPjWTfeWTTjWzPeWTTeWDPkelzrnojvr53ywLLibEvxuKj77en66OPyvq/urJrrm4XgYj/ws6P88/HvsqHjdFbfYT/jclP65uHywrThaUjmgmf88e/rmoTlfmLxuKnxvK7tpZLibk/mhWruqZf88e7////3187haEfpk3v43NX88u/rmoPgZELywbPgY0H1zsP88/DmgWX43NT87+zgZ0XvsaDoj3bf5e7Gz+D2+PvW3em2wtjks6nwt6fN1eT9+ffgZUP0yr/5+vzv8vehsM3H0OHrnonywLNkfaxRbqL3+ftVcaSKncDxuqvurZuntdB+k7r+/v/eWjPmdinumx3wnxvlcyrojXT//vb/99P/87z2v1r0rRb8ywz90gr6xQ7ha0v//vj+5nP90w/90Qr1shXpgyXeWzP+42X90AvxphnzqRjypxnsjSLhZi/+42T3uRLuq5n//O/2uyL0rxbriSLsjiHwoRr7yg3iaC7niW/icFHiZy7zrBf5whD3uhLumR3jdlf66uXqlH3zxrrYXDTdWTPso4/d8da136bq9uX99fPeXDjcWTSkhzxquEa4dznlf2PB5LRlvEZmvUeHyXNuvlVouUh7qkKohTzmg2ffXjv+/fzG5rpas0ZkvEajiDz54NrQ68dftU1atEZit06npl+ApUKrgjvzx7vp9uV3xFy24Kj4/Pb55N/eWTXdeVfus6H77+v44Nr44Nn54tz76uYtKC1GAAAAEnRSTlMABkeVv9nzGJbxB4L0xy7jLfK6UobZAAACRklEQVQ4y4VT9XsTQRC9aKMEJsnFrb0kTbDSoUWKF5eixa24HO4Eihco7i6Lu7u35Q/jbu/2uLQfH/PTzs7bmdk3bzhOM4PRZLZYrRazyWjgOlqJzQ5en5/n/T4v2G0l7cIOpysQDIUj0VgsGgmHggGX01H03B1PJFOloFppKpkoc+uSdPII6UwWdJbNpAVPZ+29pzyXh67duvfo+ReSz5V71BwOt5CrAOhViYi9BQ1RkRPcSh/OeDovXVRVVfdB7NuvP0PE0nEnLeBK1Mj+gIFIbdBghsgkXHIRWyBJ+xuCqg0dxjpNBmwSf/ZgrewNxxEjR8nx0WPGshSpoN3AGSFE/z8Ox7MUE+oYHyEwciZvmDoTESdNpvEpU6tZirDXxJmnRei5HnH6jJkyYNbsOQwQ8Zk5iz9Kz3MR582nGRZgPSxsWLR4CUDUb+GsfIwClrIOli1fsXLValFc0yBRwVs1wFolvG79BlGyjZs2b5G54rtoJaCSAraK27bv2FnYpdzJJcw+pUlQiNwtNuqGKjfJvgl7KGDvPnF/4cDBQ41Nh9VvMqLgiNLE0ebCseNNJ042M6IkqlMUUKcATukqUKq1YcFpCjijkxUdljzuDPXPUoA8+nPn9eNmgoELePHSZbxy9dr1Gzep6NJlTr3k4BbeJnfu3rv/gJCHRZKTRPtIEi08fvKUkGeEPCfkRZFoFdnXZOHlK0Jev3n77j35UCx7dXFqP376/OUr4rfvP362Wxy2er9aWtvaWlt+d1y9/y/vv9f/DzYRmYvARwKBAAAAAElFTkSuQmCC',
},
{
name: 'Google',
url: 'https://www.google.com/search?q=%s',
keywordsUrl: 'http://google.com/complete/search?client=chrome&q=%s',
keyword: 'google.com',
icon:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACDVBMVEUAAAD+/v7+/v79/f39/f39/f39/f39/f3+/v7+/v79/f39/f39/f39/f3+/v7////9/f39/f39/f39/f3+/v7+/v79/f39/f3+/v7+/v79/f39/f3////+/v79/f3////9/f3+/v7+/v79/f39/f39/f3+/v7////9/f3+/v79/f39/f39/f3////9/f3////+/v7////85uT1pZ7wdGrsVUjqRjjqRTfsU0bvcmj1oZr85OL85+XxgXjqRDbqQzXxf3b3urXrSz7rTD/4v7r1p6DyioHuZVn0m5P3tK73ta/0m5TuZlryjob//v7rSj3tXFD5zMj5y8fsWEzzlIz0kG3vbGH+8vL+9fT+9t37uQfxdyD98O7934b7vAX1kxXrSTT3uLP8zUb5riH/+/v7whz801lChfS/1fv7vQf93H2lxfqfwfn80liqyPr8zkfkwi76/fve6v3B1/v934iztSE8qFCw3byox/rn8P7+9t/2vAmGsDM0qFNGsGLs9+/z9/5UkPVgmfaPx4JcuXXw+fL6/P93p/epyPrm9Oo+rFtMsmfC5cza7+BjsahChfH2+f634MJUtm6Nzp+q2rer27iV0qVkvHs1qFQ3oHdBiePD2Pyj2LE1pWA+jsq54cQ+rFw3qVab0rHn9et7xo82qVRnvn7X7t2h169swIJJsWU4qlZGsGNlvX3Y7t5rzgcKAAAAMXRSTlMADlqcy+367Fknn/f2nSUKjfz7iwkm1dIkMuvqMAuKD/UNV5nI+VYMmiOH09AI6CIvZ7+SkQAAAe5JREFUOMtjYIADRiZmFlY2NnYWZg5GBkzAycVtCAc8vHxo0vwCgoYoQEhYBFleVMwQA4hLIOQlpcBCRsYmpmbmFpZW1mCutAxcP1jextbOHgrsHGzAKqBmyILNd3SyRwLOLmBbIO4QALFd7VGBG9gWObD/QO53hAi7e3h6efsAGb5+EL+ALOECOc8fJB0QCHF/UHAIzCvMDAzyoPAJBcmHhcOEI+B+VVBkYAJSkVHR9vamMYZYgBIDM5CMjYuLT0jEJm+ozMACJJPi4uKSU7AqUGFgBZKpQAVpUJF0BMgABRYDG5DMBCrIwlSQDeSq4lOQA1bADrUiF1NBHtgKkCPzgQoKCpHdVgRSUAx2JMibJXFxpWXlyAoqQAoqwd7kAJJV1TW1tXX1CPmGRqB8E4ilxsDIA6Saa4GgpRUm39YOMqADFNTqDAy8QLqzC6Siuwci39vXD5RvnABkagBjk08IyJhYCwaTJk+ZOm36jNqZs9LTZwOF2TRBCUIYpGtOLQqYOw8UCIZakCSvDWLPX4CsYOEikJiOLiRRSkiDeIuXLIXLL1sOEtHThyVrGbAKw84VK1etXrhm7bR1YK6eASJjSIhjRrSOPnLWEpETQpVm09JFy50SzAoIaQUNTSz5W1FJWUVaVVVaRVlNHSEKACBb24XRQm7rAAAAAElFTkSuQmCC',
},
{
name: 'Bing',
url: 'https://www.bing.com/search?q=%s',
keywordsUrl: '',
keyword: 'bing.com',
icon:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAAANlBMVEUAfX3k8/MAiop7trYAgoL6//8AdXULhIT///+exMQ4l5ew0dFLoaG929v0+/vQ5+eVwsIcjo4kKiufAAAAZElEQVQYlY2QyQ6AMAhEpy0Fuqr//7NWo108OQcSHsMSICJEMoSWR8sLQNCCD/A/AEALYFMy3+wFQdWkg0aLNaoaMM3gbQaV3eVww9HKajL60KbqmJ61QrvXBPQ7WrCRp0vXB5xZ6wZZjwmZbQAAAABJRU5ErkJggg==',
},
{
name: 'Ekoru',
url: 'https://www.ekoru.org/?ext=wexond&q=%s',
keywordsUrl: 'http://ac.ekoru.org/?ext=wexond&q=%s',
name: 'Yahoo!',
url: 'https://search.yahoo.com/search?p=%s',
keywordsUrl: '',
keyword: 'yahoo.com',
icon:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAABTVBMVEVHcEwGoc4fq68HodEirbQ/v78RmcxNsqIupbJOsVAZocIHntchqb0KoNgZqL0LoNk2r38HoNYaq71Ss0VVt1BZtEJUsj+fyjtuwGUXms8Poc0HnNMFpM8JotISoMgRn9UQn9EInNVdulhyuUIYqLMHp8p4vUAZo8mKxD4Gn9YNpcsGoNSMxj8Wq751uj8Fpdo0r3sFpNwsrotUtEiTyk1SsjtSskVQtWJItGmMxTyHxUgmsLAsqaI0q4kOmNQ9sG4yr4Jvu0krsakcpbRjuk6Dwz0KqMtBsm4Upr6AwkBCsW4NqMcurpM9sHaUyFhwvEdIsVSSylUwsKWizkqkzkBdt0lct0Iqrq5fuER/wT6Bwj8lrbNwwHC21UNPt4cPpMRIt49Zu38Up7szspglrKdEtHc8solGt5BavH5OuIB3xFqAxVMFp9hAsn9lv3FSuK/uAAAAaHRSTlMA/P6dPgQPCxUwIcYubZRWYLpdQthwTGn6GlGl9O6AOefX9SDzzpbvtqz54uCdRvjxyfuV2Thg9+166sJzeVmu3Mrw2tqi27Xz0W+p6aJdu4mYolKqrX+WyVqKgrEqx/bYvP///////rhNcOkAAAD4SURBVBiVJY9VQ8IAGAC/jSUrxpKUkO4uu7u7XQPq/3904r3dvR0AACGUKghSKQkkLFgS3612s9lG47HgwtPosKPSPfViw2D8QomoLFFhvpbE6HUjQAJXHqrsxPU8d4un9w8EaFm3wcnPuFiszs2adCJCw1HCXrWOQ3Z3hoZPy/Bl9+9nT+T1GZk9dI+PDD8oV/PHV+3mEs9Pc6FlaHx3nvURgNal8tM9P7TMB+xjPNK6L9S5mQttA2dk+tibrn+y9YwRWOUhwpgyTWAYQW+a8egKCxBNOLLSk+52HCRAEv4LziUsezCwLSQW+b/F15hUoZBKJ6k/+wXyrSm5N9MsrwAAAABJRU5ErkJggg==',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAA3NCSVQICAjb4U/gAAABnklEQVQokZWSPUyTYRSFn/t+rTX9EFp/SICwqIumMUwaJxdlYHAgBJwMCa5sLMaITizGARYSNTGROOAAokMHYggLBBIGCIHESBggwfJTQGgLL/3e61CxVWOa3ukO59xz7z1HOmWBSspUhAZCZREZdSfoGSQqprzCgQa945dHT5qef7mSVVdUCFQtapCICGBVAzSE1MUjibvngOSrrTDyixCoXm+P3u6Ipb7akcdbBlp6LzQ2nZ14nW7uugjsb+Qnh/fOSwiQTllQRZC3mgAeyWIO915vAA9l8Z0mgKEn68m+dEE8BIiQ0WD20+7N+/HWwUu5Hw6YGdu99zSmeeyh+9i3U316rRR8cKqx2nB/6lomHdicizeEu2X5zoua/e/5Dy+36ynsX/JWI7K2efxtJnP1lu/jrc5lE93Rtp56YH36eGUq550yim/1McPPNgr9WH9q6XPWHuj2ql2ZOjK/5/9lXN4qkE0HE0N7MbwH1fMGqvCkiC8hHKFdA41A8s2mjzEiNXj/Wiml4TtUZ3E+JiL/TcAfK1WJKRuWitP6EzoWnEvW4UekAAAAAElFTkSuQmCC',
},
{
name: 'Ecosia',
url: 'https://www.ecosia.org/search?q=%s',
keywordsUrl: '',
keyword: 'ecosia.org',
icon:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAylJREFUOI1Nk01sVFUYhp9z7r1zO1NK25ShtkxmSqE0VisWf0KbatkQ0USnEhPiwh+SGgkkLowQ6EKKWGgTNi4ICxtQMNFEMG1iMBqURKNMgtYwtKkUq20tFFoYxjudmc7ce+5xgdZ+2zfPu3m/R7Ds9P44oGOoYg9uoQ2IACaaG1jWMDJwDNNMiCODS4xYgg90gqFP4nk7/a1dyOZWCFffD527MDYMX5+CRecCwtr6X4lYgnEv6ujGLeKNwzjeON/+keTjiTBKCxrLVxBfH+Op2jD68+OIy0NTWKV14sgghu7uBJMPdHXjDrGnj/6fL1NMvc71tM25+QaigRlGnRzvX5tjau4W255/CSPtVIjZa+097U1nhD4Qh0VH03uexO2zdP+imSpGsKTHm2suEK89Q8EvI6dWEJJTmIEXWBs7idnzMixmn5Yo9yjtO/hLC1p/qCCjwqw0XJ4ovcGW8DdkvBqUH8IWPp6Oks0P8fdCAv+ZLvAKb0vcQhvNm/ny+gwNpkQIjRQeu+pPYwnNWKYJV5sAGMJDimrS6S/QjY+DKrZItFrtV0boqBxgQ/Aeed8iaqeIhS4hhKZ5ZRJLuBS1yW+ZjdhGDuVeRVeWAyIi0WitIWQoDj/4Hg+HZpnIV7MnOcBsPgZA0EzRO/4Wxye3c2pyF8l0C4YGwJdI4yZz09glLVhinptuGbZUjOVqmMnHsKXDuZmdjGZrcVSQT+ceQ5mPIu7eBpiWBEouyl+/Y1XVc7wz2o/jBZFoTOFTai6QV2Us+JqQ9JBocn6OhppnYeRHMAOJ+zPm04p3z8rTsw77hq8SswMoLVgXvIMpFFeyayiVLvOuxyt1EQ5tfgS644C5VuJrKK/az4l9vNpUz7FNDzFRcMn6HqPZKq4srEbrAr8XinSti3KorQX94UEwjU+QYvLfV34RDP8jQlWvsbufW8EKvhr/k5FUGuVrNlSUsW19HfW2RA/0IKaTCQi0ir7BZTJ1d4L291LI9PFkp2RTB6yqASnh3h0YuQTffwYloRN47BZHl8n0v87bQShwCwdRbgdaPQBCIEQKI/ATpr0XA0Tv0BLzDxefVnicNn0wAAAAAElFTkSuQmCC',
},
{
name: 'Yahoo!',
url: 'https://search.yahoo.com/search?p=%s',
keywordsUrl: '',
name: 'Ekoru',
url: 'https://www.ekoru.org/?ext=wexond&q=%s',
keywordsUrl: 'http://ac.ekoru.org/?ext=wexond&q=%s',
keyword: 'ekoru.org',
icon:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAA3NCSVQICAjb4U/gAAABnklEQVQokZWSPUyTYRSFn/t+rTX9EFp/SICwqIumMUwaJxdlYHAgBJwMCa5sLMaITizGARYSNTGROOAAokMHYggLBBIGCIHESBggwfJTQGgLL/3e61CxVWOa3ukO59xz7z1HOmWBSspUhAZCZREZdSfoGSQqprzCgQa945dHT5qef7mSVVdUCFQtapCICGBVAzSE1MUjibvngOSrrTDyixCoXm+P3u6Ipb7akcdbBlp6LzQ2nZ14nW7uugjsb+Qnh/fOSwiQTllQRZC3mgAeyWIO915vAA9l8Z0mgKEn68m+dEE8BIiQ0WD20+7N+/HWwUu5Hw6YGdu99zSmeeyh+9i3U316rRR8cKqx2nB/6lomHdicizeEu2X5zoua/e/5Dy+36ynsX/JWI7K2efxtJnP1lu/jrc5lE93Rtp56YH36eGUq550yim/1McPPNgr9WH9q6XPWHuj2ql2ZOjK/5/9lXN4qkE0HE0N7MbwH1fMGqvCkiC8hHKFdA41A8s2mjzEiNXj/Wiml4TtUZ3E+JiL/TcAfK1WJKRuWitP6EzoWnEvW4UekAAAAAElFTkSuQmCC',
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4U/gAAABTVBMVEVHcEwGoc4fq68HodEirbQ/v78RmcxNsqIupbJOsVAZocIHntchqb0KoNgZqL0LoNk2r38HoNYaq71Ss0VVt1BZtEJUsj+fyjtuwGUXms8Poc0HnNMFpM8JotISoMgRn9UQn9EInNVdulhyuUIYqLMHp8p4vUAZo8mKxD4Gn9YNpcsGoNSMxj8Wq751uj8Fpdo0r3sFpNwsrotUtEiTyk1SsjtSskVQtWJItGmMxTyHxUgmsLAsqaI0q4kOmNQ9sG4yr4Jvu0krsakcpbRjuk6Dwz0KqMtBsm4Upr6AwkBCsW4NqMcurpM9sHaUyFhwvEdIsVSSylUwsKWizkqkzkBdt0lct0Iqrq5fuER/wT6Bwj8lrbNwwHC21UNPt4cPpMRIt49Zu38Up7szspglrKdEtHc8solGt5BavH5OuIB3xFqAxVMFp9hAsn9lv3FSuK/uAAAAaHRSTlMA/P6dPgQPCxUwIcYubZRWYLpdQthwTGn6GlGl9O6AOefX9SDzzpbvtqz54uCdRvjxyfuV2Thg9+166sJzeVmu3Mrw2tqi27Xz0W+p6aJdu4mYolKqrX+WyVqKgrEqx/bYvP///////rhNcOkAAAD4SURBVBiVJY9VQ8IAGAC/jSUrxpKUkO4uu7u7XQPq/3904r3dvR0AACGUKghSKQkkLFgS3612s9lG47HgwtPosKPSPfViw2D8QomoLFFhvpbE6HUjQAJXHqrsxPU8d4un9w8EaFm3wcnPuFiszs2adCJCw1HCXrWOQ3Z3hoZPy/Bl9+9nT+T1GZk9dI+PDD8oV/PHV+3mEs9Pc6FlaHx3nvURgNal8tM9P7TMB+xjPNK6L9S5mQttA2dk+tibrn+y9YwRWOUhwpgyTWAYQW+a8egKCxBNOLLSk+52HCRAEv4LziUsezCwLSQW+b/F15hUoZBKJ6k/+wXyrSm5N9MsrwAAAABJRU5ErkJggg==',
},
];

export const DEFAULT_SETTINGS: ISettings = {
theme: 'wexond-light',
darkContents: false,
shield: true,
multrin: true,
animations: true,
bookmarksBar: false,
suggestions: true,
themeAuto: true,
searchEngines: DEFAULT_SEARCH_ENGINES,
searchEngine: 0,
startupBehavior: {
type: 'empty',
},
warnOnQuit: false,
version: 2,
downloadsDialog: false,
downloadsPath: remote
? remote.app.getPath('downloads')
: app
? app.getPath('downloads')
: '',
doNotTrack: true,
topBarVariant: 'default',
};
4 changes: 2 additions & 2 deletions src/constants/tabs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WEBUI_BASE_URL, WEBUI_URL_SUFFIX } from './files';
import { getWebUIURL } from '~/common/webui';

export const NEWTAB_URL = `${WEBUI_BASE_URL}newtab${WEBUI_URL_SUFFIX}`;
export const NEWTAB_URL = getWebUIURL('newtab');

export const defaultTabOptions: chrome.tabs.CreateProperties = {
url: NEWTAB_URL,
Expand Down
3 changes: 3 additions & 0 deletions src/constants/web-contents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ZOOM_FACTOR_INCREMENT = 0.2;
export const ZOOM_FACTOR_MAX = 5;
export const ZOOM_FACTOR_MIN = 0.2;
11 changes: 11 additions & 0 deletions src/interfaces/download-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ export interface IDownloadItem {
receivedBytes?: number;
totalBytes?: number;
savePath?: string;
url?: string;
id?: string;
completed?: boolean;
paused?: boolean;
canceled?: boolean;
menuIsOpen?: boolean;
openWhenDone?: boolean;
}

export interface IElectronDownloadItem {
item?: Electron.DownloadItem;
webContents?: Electron.WebContents;
id?: string;
}
14 changes: 14 additions & 0 deletions src/interfaces/extensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type BrowserActionChangeType =
| 'setPopup'
| 'setBadgeText'
| 'setTitle'
| 'setIcon'
| 'setBadgeBackgroundColor';

export const BROWSER_ACTION_METHODS: BrowserActionChangeType[] = [
'setPopup',
'setBadgeText',
'setTitle',
'setIcon',
'setBadgeBackgroundColor',
];
1 change: 1 addition & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './weather';
export * from './form-fill';
export * from './storage';
export * from './bounds';
export * from './extensions';
15 changes: 10 additions & 5 deletions src/interfaces/settings.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
export interface ISearchEngine {
name: string;
url: string;
keywordsUrl: string;
icon: string;
name?: string;
url?: string;
keywordsUrl?: string;
keyword?: string;
icon?: string;
}

export interface IStartupBehavior {
type: 'continue' | 'urls' | 'empty';
}

export type TopBarVariant = 'default' | 'compact';

export interface ISettings {
theme: string;
themeAuto: boolean;
Expand All @@ -21,8 +24,10 @@ export interface ISettings {
searchEngines: ISearchEngine[];
startupBehavior: IStartupBehavior;
warnOnQuit: boolean;
version: string;
version: number;
darkContents: boolean;
downloadsDialog: boolean;
downloadsPath: string;
doNotTrack: boolean;
topBarVariant: TopBarVariant;
}
3 changes: 2 additions & 1 deletion src/interfaces/suggestion.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export interface ISuggestion {
primaryText: string;
primaryText?: string;
secondaryText?: string;
id?: number;
favicon?: string;
canSuggest?: boolean;
isSearch?: boolean;
hovered?: boolean;
url?: string;
}
13 changes: 13 additions & 0 deletions src/interfaces/tabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type TabEvent =
| 'load-commit'
| 'url-updated'
| 'title-updated'
| 'favicon-updated'
| 'did-navigate'
| 'loading'
| 'pinned'
| 'credentials'
| 'blocked-ad'
| 'zoom-updated'
| 'media-playing'
| 'media-paused';
30 changes: 15 additions & 15 deletions src/interfaces/theme.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
export interface ITheme {
'titlebar.backgroundColor': string;

'addressbar.backgroundColor': string;
'addressbar.textColor': string;

'toolbar.backgroundColor': string;
'toolbar.bottomLine.backgroundColor': string;
'toolbar.lightForeground': boolean;
'toolbar.separator.color': string;

'tab.backgroundOpacity': number;
'tab.selectedHover.backgroundOpacity': number;
'tab.hover.backgroundOpacity': number;
'tab.selected.textColor': string;
'tab.textColor': string;
'tab.allowLightBackground': boolean;
'tab.selected.textColor': string;

'control.backgroundColor': string;
'control.hover.backgroundColor': string;
Expand All @@ -22,16 +23,8 @@ export interface ITheme {
'dialog.textColor': string;
'dialog.lightForeground': boolean;

'searchBox.input.textColor': string;
'searchBox.input.lightForeground': boolean;
'searchBox.input.backgroundColor': string;

'searchBox.subHeading.backgroundColor': string;
'searchBox.subHeading.textColor': string;

'searchBox.suggestions.textColor': string;
'searchBox.suggestions.backgroundColor': string;
'searchBox.suggestions.lightForeground': boolean;
'searchBox.backgroundColor': string;
'searchBox.lightForeground': boolean;

'pages.backgroundColor': string;
'pages.lightForeground': boolean;
Expand All @@ -40,11 +33,18 @@ export interface ITheme {
'pages.navigationDrawer2.backgroundColor': string;

'dropdown.backgroundColor': string;
'dropdown.backgroundColor.translucent': string;
'dropdown.separator.color': string;

backgroundColor: string;
accentColor: string;

animations?: boolean;

titlebarHeight?: number;
tabHeight?: number;
tabMarginTop?: number;
isCompact?: boolean;

dark?: boolean;
}
4 changes: 4 additions & 0 deletions src/interfaces/urls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface IURLSegment {
value: string;
grayOut: boolean;
}
114 changes: 114 additions & 0 deletions src/main/application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { app, ipcMain, Menu } from 'electron';
import { isAbsolute, extname } from 'path';
import { existsSync } from 'fs';
import { SessionsService } from './sessions-service';
import { checkFiles } from '~/utils/files';
import { Settings } from './models/settings';
import { isURL, prefixHttp } from '~/utils';
import { WindowsService } from './windows-service';
import { StorageService } from './services/storage';
import { getMainMenu } from './menus/main';
import { runAutoUpdaterService } from './services';
import { DialogsService } from './services/dialogs-service';
import { requestAuth } from './dialogs/auth';
import { NetworkServiceHandler } from './network/network-service-handler';
import { ExtensionServiceHandler } from './extension-service-handler';

export class Application {
public static instance = new Application();

public sessions: SessionsService;

public settings = new Settings();

public storage = new StorageService();

public windows = new WindowsService();

public dialogs = new DialogsService();

public start() {
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
app.quit();
return;
} else {
app.on('second-instance', async (e, argv) => {
const path = argv[argv.length - 1];

if (isAbsolute(path) && existsSync(path)) {
if (process.env.NODE_ENV !== 'development') {
const path = argv[argv.length - 1];
const ext = extname(path);

if (ext === '.html') {
this.windows.current.win.focus();
this.windows.current.viewManager.create({
url: `file:///${path}`,
active: true,
});
}
}
return;
} else if (isURL(path)) {
this.windows.current.win.focus();
this.windows.current.viewManager.create({
url: prefixHttp(path),
active: true,
});
return;
}

this.windows.open();
});
}

app.on('login', async (e, webContents, request, authInfo, callback) => {
e.preventDefault();

const window = this.windows.findByBrowserView(webContents.id);
const credentials = await requestAuth(
window.win,
request.url,
webContents.id,
);

if (credentials) {
callback(credentials.username, credentials.password);
}
});

ipcMain.on('create-window', (e, incognito = false) => {
this.windows.open(incognito);
});

this.onReady();
}

private async onReady() {
await app.whenReady();

new ExtensionServiceHandler();

NetworkServiceHandler.get();

checkFiles();

this.storage.run();
this.dialogs.run();

this.windows.open();

this.sessions = new SessionsService();

Menu.setApplicationMenu(getMainMenu());
runAutoUpdaterService();

app.on('activate', () => {
if (this.windows.list.filter((x) => x !== null).length === 0) {
this.windows.open();
}
});
}
}
87 changes: 47 additions & 40 deletions src/main/dialogs/add-bookmark.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,49 @@
import { AppWindow } from '../windows';
import { Dialog } from '.';
import { windowsManager } from '..';

const WIDTH = 350;

export class AddBookmarkDialog extends Dialog {
public visible = false;

public left = 0;

constructor(appWindow: AppWindow) {
super(appWindow, {
name: 'add-bookmark',
bounds: {
width: WIDTH,
height: 228,
y: 34,
},
});
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design';
import { IBookmark } from '~/interfaces';

export const showAddBookmarkDialog = (
browserWindow: BrowserWindow,
x: number,
y: number,
data?: {
url: string;
title: string;
bookmark?: IBookmark;
favicon?: string;
},
) => {
if (!data) {
const {
url,
title,
bookmark,
favicon,
} = Application.instance.windows.current.viewManager.selected;
data = {
url,
title,
bookmark,
favicon,
};
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();

super.rearrange({
x: Math.round(Math.min(this.left - WIDTH / 2, width - WIDTH)),
});
}

public show() {
super.show();
const view = windowsManager.currentWindow.viewManager.selected;

this.webContents.send('visible', true, {
url: view.webContents.getURL(),
title: view.webContents.getTitle(),
bookmark: view.bookmark,
favicon: view.favicon,
});
}
}
const dialog = Application.instance.dialogs.show({
name: 'add-bookmark',
browserWindow,
getBounds: () => ({
width: 366,
height: 240,
x: x - 366 + DIALOG_MARGIN,
y: y - DIALOG_MARGIN_TOP,
}),
onWindowBoundsUpdate: () => dialog.hide(),
});

if (!dialog) return;

dialog.on('loaded', (e) => {
e.reply('data', data);
});
};
77 changes: 41 additions & 36 deletions src/main/dialogs/auth.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,49 @@
import { ipcMain } from 'electron';
import { AppWindow } from '../windows';
import { TOOLBAR_HEIGHT } from '~/constants/design';
import { Dialog } from '.';
import { VIEW_Y_OFFSET } from '~/constants/design';
import { BrowserWindow } from 'electron';
import { Application } from '../application';

const WIDTH = 400;
const HEIGHT = 500;
export const requestAuth = (
browserWindow: BrowserWindow,
url: string,
tabId: number,
): Promise<{ username: string; password: string }> => {
return new Promise((resolve, reject) => {
const appWindow = Application.instance.windows.fromBrowserWindow(
browserWindow,
);

export class AuthDialog extends Dialog {
public constructor(appWindow: AppWindow) {
super(appWindow, {
const tab = appWindow.viewManager.views.get(tabId);
tab.requestedAuth = { url };

const dialog = Application.instance.dialogs.show({
name: 'auth',
bounds: {
width: WIDTH,
height: HEIGHT,
y: TOOLBAR_HEIGHT,
browserWindow,
getBounds: () => {
const { width } = browserWindow.getContentBounds();
return {
width: 400,
height: 500,
x: width / 2 - 400 / 2,
y: VIEW_Y_OFFSET,
};
},
tabAssociation: {
tabId,
getTabInfo: (tabId) => {
const tab = appWindow.viewManager.views.get(tabId);
return tab.requestedAuth;
},
},
onWindowBoundsUpdate: (disposition) => {
if (disposition === 'resize') dialog.rearrange();
},
});
}

public requestAuth(
url: string,
): Promise<{ username: string; password: string }> {
return new Promise(resolve => {
this.show();

this.webContents.send('request-auth', url);

ipcMain.once(`request-auth-result-${this.appWindow.id}`, (e, result) => {
this.hide();
resolve(result);
});
});
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();
if (!dialog) return;

super.rearrange({
x: Math.round(width / 2 - WIDTH / 2),
y: TOOLBAR_HEIGHT,
dialog.on('result', (e, result) => {
resolve(result);
dialog.hide();
});
}
}
});
};
6 changes: 3 additions & 3 deletions src/main/dialogs/credentials.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TOOLBAR_HEIGHT } from '~/constants/design';
import { VIEW_Y_OFFSET } from '~/constants/design';
import { AppWindow } from '../windows';
import { Dialog } from '.';

Expand All @@ -12,13 +12,13 @@ export class CredentialsDialog extends Dialog {
bounds: {
height: HEIGHT,
width: WIDTH,
y: TOOLBAR_HEIGHT,
y: VIEW_Y_OFFSET,
},
});
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();
const { width } = this.appWindow.win.getContentBounds();
super.rearrange({
x: width - WIDTH,
});
Expand Down
168 changes: 107 additions & 61 deletions src/main/dialogs/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BrowserView, app, ipcMain } from 'electron';
import { BrowserView, app, ipcMain, BrowserWindow } from 'electron';
import { join } from 'path';
import { AppWindow } from '../windows';
import { roundifyRectangle } from '../services/dialogs-service';

interface IOptions {
name: string;
Expand All @@ -18,8 +18,9 @@ interface IRectangle {
height?: number;
}

export class Dialog extends BrowserView {
public appWindow: AppWindow;
export class PersistentDialog {
public browserWindow: BrowserWindow;
public browserView: BrowserView;

public visible = false;

Expand All @@ -30,119 +31,164 @@ export class Dialog extends BrowserView {
height: 0,
};

public name: string;

private timeout: any;
private hideTimeout: number;
private name: string;

public constructor(
appWindow: AppWindow,
{ bounds, name, devtools, hideTimeout, webPreferences }: IOptions,
) {
super({
private loaded = false;
private showCallback: any = null;

public constructor({
bounds,
name,
devtools,
hideTimeout,
webPreferences,
}: IOptions) {
this.browserView = new BrowserView({
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
affinity: 'dialog',
enableRemoteModule: true,
worldSafeExecuteJavaScript: false,
...webPreferences,
},
});

this.appWindow = appWindow;
this.bounds = { ...this.bounds, ...bounds };
this.hideTimeout = hideTimeout;
this.name = name;

ipcMain.on(`hide-${this.webContents.id}`, () => {
this.hide();
const { webContents } = this.browserView;

ipcMain.on(`hide-${webContents.id}`, () => {
this.hide(false, false);
});

if (process.env.NODE_ENV === 'development') {
this.webContents.loadURL(`http://localhost:4444/${name}.html`);
if (devtools) {
this.webContents.openDevTools({ mode: 'detach' });
webContents.once('dom-ready', () => {
this.loaded = true;

if (this.showCallback) {
this.showCallback();
this.showCallback = null;
}
});

if (process.env.NODE_ENV === 'development') {
this.webContents.loadURL(`http://localhost:4444/${this.name}.html`);
} else {
this.webContents.loadURL(
join('file://', app.getAppPath(), `build/${name}.html`),
join('file://', app.getAppPath(), `build/${this.name}.html`),
);
}
}

public get webContents() {
return this.browserView.webContents;
}

public get id() {
return this.webContents.id;
}

public rearrange(rect: IRectangle = {}) {
this.bounds = {
height: rect.height || this.bounds.height,
width: rect.width || this.bounds.width,
x: rect.x || this.bounds.x,
y: rect.y || this.bounds.y,
};
this.bounds = roundifyRectangle({
height: rect.height || this.bounds.height || 0,
width: rect.width || this.bounds.width || 0,
x: rect.x || this.bounds.x || 0,
y: rect.y || this.bounds.y || 0,
});

if (this.visible) {
this.setBounds(this.bounds as any);
this.browserView.setBounds(this.bounds as any);
}
}

public toggle() {
if (!this.visible) this.show();
else this.hide();
}
public show(browserWindow: BrowserWindow, focus = true, waitForLoad = true) {
return new Promise((resolve) => {
this.browserWindow = browserWindow;

public show(focus = true) {
if (this.visible) return;
clearTimeout(this.timeout);

this.visible = true;
browserWindow.webContents.send(
'dialog-visibility-change',
this.name,
true,
);

clearTimeout(this.timeout);
const callback = () => {
if (this.visible) {
if (focus) this.webContents.focus();
return;
}

this.visible = true;

browserWindow.addBrowserView(this.browserView);
this.rearrange();

if (process.platform === 'darwin') {
setTimeout(() => {
this.bringToTop();
if (focus) this.webContents.focus();
});
} else {
this.bringToTop();
if (focus) this.webContents.focus();
}

this.rearrange();
resolve();
};

if (!this.loaded && waitForLoad) {
this.showCallback = callback;
return;
}

callback();
});
}

public hideVisually() {
this.webContents.send('visible', false);
this.send('visible', false);
}

private _hide() {
this.setBounds({
height: this.bounds.height,
width: 1,
x: 0,
y: -this.bounds.height + 1,
});
public send(channel: string, ...args: any[]) {
this.webContents.send(channel, ...args);
}

public hide(bringToTop = false) {
public hide(bringToTop = false, hideVisually = true) {
if (!this.browserWindow) return;

if (hideVisually) this.hideVisually();

if (!this.visible) return;

this.browserWindow.webContents.send(
'dialog-visibility-change',
this.name,
false,
);

if (bringToTop) {
this.bringToTop();
}

if (!this.visible) return;

clearTimeout(this.timeout);

if (this.hideTimeout) {
this.timeout = setTimeout(() => this._hide(), this.hideTimeout);
this.timeout = setTimeout(() => {
this.browserWindow.removeBrowserView(this.browserView);
}, this.hideTimeout);
} else {
this._hide();
this.browserWindow.removeBrowserView(this.browserView);
}

this.visible = false;

this.hideVisually();

this.appWindow.fixDragging();
// this.appWindow.fixDragging();
}

public bringToTop() {
this.appWindow.removeBrowserView(this);
this.appWindow.addBrowserView(this);
this.browserWindow.removeBrowserView(this.browserView);
this.browserWindow.addBrowserView(this.browserView);
}

public destroy() {
this.browserView.destroy();
this.browserView = null;
}
}
93 changes: 46 additions & 47 deletions src/main/dialogs/downloads.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,46 @@
import { AppWindow } from '../windows';
import { Dialog } from '.';
import { ipcMain } from 'electron';

const WIDTH = 350;

export class DownloadsDialog extends Dialog {
public visible = false;

private height = 0;

public left = 0;

constructor(appWindow: AppWindow) {
super(appWindow, {
name: 'downloads',
bounds: {
width: WIDTH,
height: 0,
y: 34,
},
});

ipcMain.on(`height-${this.webContents.id}`, (e, height) => {
this.height = height;
this.rearrange();
});
}

public rearrange() {
const { width, height } = this.appWindow.getContentBounds();

const maxHeight = height - 34 - 16;

super.rearrange({
x: Math.round(Math.min(this.left - WIDTH / 2, width - WIDTH)),
height: Math.round(Math.min(height, this.height + 16)),
});

this.webContents.send(`max-height`, Math.min(maxHeight, this.height));
}

public show() {
super.show();
this.webContents.send('visible', true);
}
}
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import {
DIALOG_MARGIN_TOP,
DIALOG_MARGIN,
DIALOG_TOP,
} from '~/constants/design';

export const showDownloadsDialog = (
browserWindow: BrowserWindow,
x: number,
y: number,
) => {
let height = 0;

const dialog = Application.instance.dialogs.show({
name: 'downloads-dialog',
browserWindow,
getBounds: () => {
const winBounds = browserWindow.getContentBounds();
const maxHeight = winBounds.height - DIALOG_TOP - 16;

height = Math.round(Math.min(winBounds.height, height + 28));

dialog.browserView.webContents.send(
`max-height`,
Math.min(maxHeight, height),
);

return {
x: x - 350 + DIALOG_MARGIN,
y: y - DIALOG_MARGIN_TOP,
width: 350,
height,
};
},
onWindowBoundsUpdate: () => dialog.hide(),
});

if (!dialog) return;

dialog.on('height', (e, h) => {
height = h;
dialog.rearrange();
});
};
101 changes: 47 additions & 54 deletions src/main/dialogs/extension-popup.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,51 @@
import { AppWindow } from '../windows';
import { Dialog } from '.';
import { ipcMain } from 'electron';

export class ExtensionPopup extends Dialog {
public visible = false;

private height = 512;

public left = 0;

private width = 512;

public url = '';

constructor(appWindow: AppWindow) {
super(appWindow, {
name: 'extension-popup',
bounds: {
width: 512,
height: 512,
y: 34,
},
devtools: false,
webPreferences: {
webviewTag: true,
},
});

ipcMain.on(`bounds-${this.webContents.id}`, (e, width, height) => {
this.height = height;
this.width = width;
this.rearrange();
});

this.webContents.on('will-attach-webview', (e, webPreferences, params) => {
webPreferences.additionalArguments = ['--session-id=1'];
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design';

export const showExtensionDialog = (
browserWindow: BrowserWindow,
x: number,
y: number,
url: string,
inspect = false,
) => {
if (!process.env.ENABLE_EXTENSIONS) return;

let height = 512;
let width = 512;

const dialog = Application.instance.dialogs.show({
name: 'extension-popup',
browserWindow,
getBounds: () => {
return {
x: x - width + DIALOG_MARGIN,
y: y - DIALOG_MARGIN_TOP,
height: Math.min(1024, height),
width: Math.min(1024, width),
};
},
onWindowBoundsUpdate: () => dialog.hide(),
});

if (!dialog) return;

dialog.on('bounds', (e, w, h) => {
width = w;
height = h;
dialog.rearrange();
});

dialog.browserView.webContents.on(
'will-attach-webview',
(e, webPreferences, params) => {
webPreferences.sandbox = true;
webPreferences.nodeIntegration = false;
webPreferences.contextIsolation = true;
});
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();

super.rearrange({
x: Math.round(Math.min(this.left - this.width + 6, width - this.width)),
height: Math.round(Math.min(1024, this.height)),
width: Math.round(Math.min(1024, this.width)),
});
}
},
);

public show() {
super.show();
this.webContents.send('visible', true, { url: this.url });
}
}
dialog.on('loaded', (e) => {
e.reply('data', { url, inspect });
});
};
76 changes: 34 additions & 42 deletions src/main/dialogs/find.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,37 @@
import { AppWindow } from '../windows';
import { TOOLBAR_HEIGHT } from '~/constants/design';
import { Dialog } from '.';
import { ipcMain } from 'electron';
import { VIEW_Y_OFFSET } from '~/constants/design';
import { BrowserWindow } from 'electron';
import { Application } from '../application';

const WIDTH = 400;
const HEIGHT = 64;
export const showFindDialog = (browserWindow: BrowserWindow) => {
const appWindow =
Application.instance.windows.fromBrowserWindow(browserWindow);

export class FindDialog extends Dialog {
public constructor(appWindow: AppWindow) {
super(appWindow, {
name: 'find',
bounds: {
width: WIDTH,
height: HEIGHT,
y: TOOLBAR_HEIGHT,
const dialog = Application.instance.dialogs.show({
name: 'find',
browserWindow,
devtools: false,
getBounds: () => {
const { width } = browserWindow.getContentBounds();
return {
width: 416,
height: 70,
x: width - 416,
y: VIEW_Y_OFFSET,
};
},
tabAssociation: {
tabId: appWindow.viewManager.selectedId,
getTabInfo: (tabId) => {
const tab = appWindow.viewManager.views.get(tabId);
return tab.findInfo;
},
});

ipcMain.on(`show-${this.webContents.id}`, () => {
this.show();
});
}

public show() {
super.show();
}

public find(tabId: number, data: any) {
data.visible = true;
this.show();
this.updateInfo(tabId, data);
}

public updateInfo(tabId: number, data: any) {
this.webContents.send('update-info', tabId, data);
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();
super.rearrange({
x: width - WIDTH,
});
}
}
setTabInfo: (tabId, info) => {
const tab = appWindow.viewManager.views.get(tabId);
tab.findInfo = info;
},
},
onWindowBoundsUpdate: (disposition) => {
if (disposition === 'resize') dialog.rearrange();
},
});
};
12 changes: 8 additions & 4 deletions src/main/dialogs/form-fill.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { TOOLBAR_HEIGHT } from '~/constants/design';
import { VIEW_Y_OFFSET, DIALOG_MARGIN_TOP } from '~/constants/design';
import { Dialog } from './dialog';
import { AppWindow } from '../windows';

const WIDTH = 208;
const HEIGHT = 128;
const MARGIN = 8;

export class FormFillDialog extends Dialog {
public inputRect = {
Expand All @@ -27,15 +26,20 @@ export class FormFillDialog extends Dialog {
public rearrange() {
super.rearrange({
x: this.inputRect.x - 8,
y: this.inputRect.y + this.inputRect.height + TOOLBAR_HEIGHT - MARGIN + 2,
y:
this.inputRect.y +
this.inputRect.height +
VIEW_Y_OFFSET -
DIALOG_MARGIN_TOP +
2,
});
}

public resize(count: number, hasSubtext = false) {
const itemHeight = hasSubtext ? 56 : 32;
super.rearrange({
width: WIDTH,
height: count * itemHeight + MARGIN * 2 + 16,
height: count * itemHeight + DIALOG_MARGIN_TOP * 2 + 16,
});
}
}
13 changes: 0 additions & 13 deletions src/main/dialogs/index.ts

This file was deleted.

54 changes: 23 additions & 31 deletions src/main/dialogs/menu.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { AppWindow } from '../windows';
import { MENU_WIDTH } from '~/constants/design';
import { Dialog } from '.';
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design';

const WIDTH = MENU_WIDTH;
const HEIGHT = 550;

export class MenuDialog extends Dialog {
public visible = false;

constructor(appWindow: AppWindow) {
super(appWindow, {
name: 'menu',
bounds: {
width: WIDTH,
height: HEIGHT,
y: 34,
},
devtools: false,
});
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();
super.rearrange({ x: width - WIDTH });
}

public show() {
super.show();
this.webContents.send('visible', true);
}
}
export const showMenuDialog = (
browserWindow: BrowserWindow,
x: number,
y: number,
) => {
const menuWidth = 330;
const dialog = Application.instance.dialogs.show({
name: 'menu',
browserWindow,
getBounds: () => ({
width: menuWidth,
height: 510,
x: x - menuWidth + DIALOG_MARGIN,
y: y - DIALOG_MARGIN_TOP,
}),
onWindowBoundsUpdate: () => {
dialog.hide();
},
});
};
86 changes: 46 additions & 40 deletions src/main/dialogs/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,55 @@
import { ipcMain } from 'electron';
import { TOOLBAR_HEIGHT } from '~/constants/design';
import { AppWindow } from '../windows';
import { Dialog } from '.';
import { VIEW_Y_OFFSET } from '~/constants/design';
import { BrowserWindow } from 'electron';
import { Application } from '../application';

const HEIGHT = 175;
const WIDTH = 350;
export const requestPermission = (
browserWindow: BrowserWindow,
name: string,
url: string,
details: any,
tabId: number,
): Promise<boolean> => {
return new Promise((resolve, reject) => {
if (
name === 'unknown' ||
(name === 'media' && details.mediaTypes.length === 0) ||
name === 'midiSysex'
) {
return reject('Unknown permission');
}

export class PermissionsDialog extends Dialog {
public constructor(appWindow: AppWindow) {
super(appWindow, {
const appWindow = Application.instance.windows.fromBrowserWindow(
browserWindow,
);

appWindow.viewManager.selected.requestedPermission = { name, url, details };

const dialog = Application.instance.dialogs.show({
name: 'permissions',
bounds: {
height: HEIGHT,
width: WIDTH,
y: TOOLBAR_HEIGHT,
browserWindow,
getBounds: () => ({
width: 366,
height: 165,
x: 0,
y: VIEW_Y_OFFSET,
}),
tabAssociation: {
tabId,
getTabInfo: (tabId) => {
const tab = appWindow.viewManager.views.get(tabId);
return tab.requestedPermission;
},
},
onWindowBoundsUpdate: (disposition) => {
if (disposition === 'resize') dialog.rearrange();
},
});
}

public async requestPermission(
name: string,
url: string,
details: any,
): Promise<boolean> {
return new Promise((resolve, reject) => {
if (
name === 'unknown' ||
(name === 'media' && details.mediaTypes.length === 0) ||
name === 'midiSysex'
) {
return reject('Unknown permission');
}
if (!dialog) return;

this.show();

this.webContents.send('request-permission', { name, url, details });

ipcMain.once(
`request-permission-result-${this.appWindow.id}`,
(e, r: boolean) => {
resolve(r);
this.hide();
},
);
dialog.on('result', (e, result) => {
resolve(result);
dialog.hide();
});
}
}
});
};
63 changes: 28 additions & 35 deletions src/main/dialogs/preview.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,51 @@
import { AppWindow } from '../windows';
import { MENU_WIDTH } from '~/constants/design';
import { Dialog } from '.';
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design';
import { PersistentDialog } from './dialog';
import { ERROR_PROTOCOL } from '~/constants/files';

const WIDTH = MENU_WIDTH;
const HEIGHT = 128;
const HEIGHT = 256;

export class PreviewDialog extends Dialog {
export class PreviewDialog extends PersistentDialog {
public visible = false;
public tab: { id?: number; x?: number } = {};
public tab: { id?: number; x?: number; y?: number } = {};

private timeout1: any;

constructor(appWindow: AppWindow) {
super(appWindow, {
constructor() {
super({
name: 'preview',
bounds: {
width: appWindow.getBounds().width,
height: HEIGHT,
y: 39,
},
hideTimeout: 200,
hideTimeout: 150,
});
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();
super.rearrange({ width });
const { width } = this.browserWindow.getContentBounds();
super.rearrange({ width, y: this.tab.y });
}

public show() {
clearTimeout(this.timeout1);
this.appWindow.dialogs.searchDialog.rearrangePreview(true);

super.show(false);

const tab = this.appWindow.viewManager.views.get(this.tab.id);
public async show(browserWindow: BrowserWindow) {
super.show(browserWindow, false);

const url = tab.webContents.getURL();
const title = tab.webContents.getTitle();

this.webContents.send('visible', true, {
id: tab.id,
url: url.startsWith('wexond-error') ? tab.errorURL : url,
const {
id,
url,
title,
errorURL,
} = Application.instance.windows
.fromBrowserWindow(browserWindow)
.viewManager.views.get(this.tab.id);

this.send('visible', true, {
id,
url: url.startsWith(`${ERROR_PROTOCOL}://`) ? errorURL : url,
title,
x: Math.round(this.tab.x - 8),
x: this.tab.x - 8,
});
}

public hide(bringToTop = true) {
clearTimeout(this.timeout1);
this.timeout1 = setTimeout(() => {
this.appWindow.dialogs.searchDialog.rearrangePreview(false);
}, 210);

super.hide(bringToTop);
}
}
101 changes: 45 additions & 56 deletions src/main/dialogs/search.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,81 @@
import { AppWindow } from '../windows';
import { ipcMain } from 'electron';
import { Dialog } from '.';
import { ipcMain, BrowserWindow } from 'electron';
import {
DIALOG_MIN_HEIGHT,
DIALOG_MARGIN_TOP,
DIALOG_MARGIN,
} from '~/constants/design';
import { PersistentDialog } from './dialog';
import { Application } from '../application';

const WIDTH = 800;
const HEIGHT = 56;
const HEIGHT = 80;

export class SearchDialog extends Dialog {
private queueShow = false;

private lastHeight = 0;
export class SearchDialog extends PersistentDialog {
private isPreviewVisible = false;

public constructor(appWindow: AppWindow) {
super(appWindow, {
public data = {
text: '',
x: 0,
y: 0,
width: 200,
};

public constructor() {
super({
name: 'search',
bounds: {
width: WIDTH,
height: HEIGHT,
y: 48,
},
hideTimeout: 300,

devtools: false,
});

ipcMain.on(`height-${this.webContents.id}`, (e, height) => {
const { width } = this.appWindow.getContentBounds();
ipcMain.on(`height-${this.id}`, (e, height) => {
super.rearrange({
height: this.isPreviewVisible
? Math.max(120, 56 + height)
: 56 + height,
x: Math.round(width / 2 - WIDTH / 2),
? Math.max(DIALOG_MIN_HEIGHT, HEIGHT + height)
: HEIGHT + height,
});
this.lastHeight = 56 + height;
});

ipcMain.on(`can-show-${this.webContents.id}`, () => {
if (this.queueShow) this.show();
ipcMain.on(`addressbar-update-input-${this.id}`, (e, data) => {
this.browserWindow.webContents.send('addressbar-update-input', data);
});
}

public toggle() {
if (!this.visible) this.show();
else this.hide();
}

public rearrange() {
const { width } = this.appWindow.getContentBounds();
super.rearrange({ x: Math.round(width / 2 - WIDTH / 2) });
}

public rearrangePreview(toggle: boolean) {
this.isPreviewVisible = toggle;
if (toggle) {
super.rearrange({
height: Math.max(120, this.bounds.height),
});
} else {
super.rearrange({
height: this.lastHeight,
});
}
super.rearrange({
x: this.data.x - DIALOG_MARGIN,
y: this.data.y - DIALOG_MARGIN_TOP,
width: this.data.width + 2 * DIALOG_MARGIN,
});
}

public show() {
this.appWindow.dialogs.previewDialog.hide(false);
private onResize = () => {
this.hide();
};

super.show();
public async show(browserWindow: BrowserWindow) {
super.show(browserWindow, true, false);

this.queueShow = true;
browserWindow.once('resize', this.onResize);

const selected = this.appWindow.viewManager.selected;

const url = selected.webContents.getURL();

this.webContents.send('visible', true, {
id: this.appWindow.viewManager.selectedId,
url: url.startsWith('wexond-error') ? selected.errorURL : url,
this.send('visible', true, {
id: Application.instance.windows.current.viewManager.selectedId,
...this.data,
});

this.appWindow.webContents.send('get-search-tabs');

ipcMain.once('get-search-tabs', (e, tabs) => {
this.webContents.send('search-tabs', tabs);
this.send('search-tabs', tabs);
});

browserWindow.webContents.send('get-search-tabs');
}

public hide() {
super.hide();
this.queueShow = false;
public hide(bringToTop = false) {
super.hide(bringToTop);
this.browserWindow.removeListener('resize', this.onResize);
}
}
49 changes: 21 additions & 28 deletions src/main/dialogs/tabgroup.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,24 @@
import { AppWindow } from '../windows';
import { TOOLBAR_HEIGHT } from '~/constants/design';
import { Dialog } from '.';
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design';

const WIDTH = 250;
const HEIGHT = 150;
export const showTabGroupDialog = (
browserWindow: BrowserWindow,
tabGroup: any,
) => {
const dialog = Application.instance.dialogs.show({
name: 'tabgroup',
browserWindow,
getBounds: () => ({
width: 266,
height: 180,
x: tabGroup.x - DIALOG_MARGIN,
y: tabGroup.y - DIALOG_MARGIN_TOP,
}),
onWindowBoundsUpdate: () => dialog.hide(),
});

export class TabGroupDialog extends Dialog {
public visible = false;
if (!dialog) return;

constructor(appWindow: AppWindow) {
super(appWindow, {
name: 'tabgroup',
bounds: {
width: WIDTH,
height: HEIGHT,
y: TOOLBAR_HEIGHT - 3,
},
});
}

public rearrange() {
super.rearrange({ x: this.bounds.x - 20 });
}

public edit(tabGroup: any) {
this.bounds.x = Math.round(tabGroup.x);
super.show();
this.webContents.send('visible', true, tabGroup);
}
}
dialog.handle('tabgroup', () => tabGroup);
};
28 changes: 28 additions & 0 deletions src/main/dialogs/zoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { BrowserWindow } from 'electron';
import { Application } from '../application';
import { DIALOG_MARGIN_TOP, DIALOG_MARGIN } from '~/constants/design';

export const showZoomDialog = (
browserWindow: BrowserWindow,
x: number,
y: number,
) => {
const tabId = Application.instance.windows.fromBrowserWindow(browserWindow)
.viewManager.selectedId;

const dialog = Application.instance.dialogs.show({
name: 'zoom',
browserWindow,
getBounds: () => ({
width: 280,
height: 100,
x: x - 280 + DIALOG_MARGIN,
y: y - DIALOG_MARGIN_TOP,
}),
onWindowBoundsUpdate: () => dialog.hide(),
});

if (!dialog) return;

dialog.handle('tab-id', () => tabId);
};
29 changes: 29 additions & 0 deletions src/main/extension-service-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RpcMainEvent, RpcMainHandler } from '@wexond/rpc-electron';
import { session, webContents } from 'electron';
import {
extensionMainChannel,
ExtensionMainService,
} from '~/common/rpc/extensions';
import { Application } from './application';

export class ExtensionServiceHandler
implements RpcMainHandler<ExtensionMainService> {
constructor() {
extensionMainChannel.getReceiver().handler = this;
}

inspectBackgroundPage(e: RpcMainEvent, id: string): void {
webContents
.getAllWebContents()
.find(
(x) =>
x.session === Application.instance.sessions.view &&
new URL(x.getURL()).hostname === id,
)
.openDevTools({ mode: 'detach' });
}

uninstall(e: RpcMainEvent, id: string): void {
Application.instance.sessions.uninstallExtension(id);
}
}
47 changes: 40 additions & 7 deletions src/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
import { ipcMain, app, webContents } from 'electron';
import { setIpcMain } from '@wexond/rpc-electron';
setIpcMain(ipcMain);

require('@electron/remote/main').initialize();

if (process.env.NODE_ENV === 'development') {
require('source-map-support').install();
}

import { platform } from 'os';
import { WindowsManager } from './windows-manager';
import { Application } from './application';

export const isNightly = app.name === 'wexond-nightly';

app.allowRendererProcessReuse = true;
app.name = 'Wexond';
app.name = isNightly ? 'Wexond Nightly' : 'Wexond';

(process.env as any)['ELECTRON_DISABLE_SECURITY_WARNINGS'] = true;

app.commandLine.appendSwitch('--enable-transparent-visuals');
app.commandLine.appendSwitch('--enable-parallel-downloading');
app.commandLine.appendSwitch(
'enable-features',
'CSSColorSchemeUARendering, ImpulseScrollAnimations, ParallelDownloading',
);

if (process.env.NODE_ENV === 'development') {
app.commandLine.appendSwitch('remote-debugging-port', '9222');
}

ipcMain.setMaxListeners(0);

// app.setAsDefaultProtocolClient('http');
// app.setAsDefaultProtocolClient('https');

export const windowsManager = new WindowsManager();
const application = Application.instance;
application.start();

process.on('uncaughtException', error => {
process.on('uncaughtException', (error) => {
console.error(error);
});

Expand All @@ -25,13 +46,17 @@ app.on('window-all-closed', () => {
}
});

ipcMain.on('get-webcontents-id', e => {
ipcMain.on('get-webcontents-id', (e) => {
e.returnValue = e.sender.id;
});

ipcMain.on('get-window-id', (e) => {
e.returnValue = (e.sender as any).windowId;
});

ipcMain.handle(
`web-contents-call`,
async (e, { webContentsId, method, args }) => {
async (e, { webContentsId, method, args = [] }) => {
const wc = webContents.fromId(webContentsId);
const result = (wc as any)[method](...args);

Expand All @@ -44,3 +69,11 @@ ipcMain.handle(
}
},
);

// We need to prevent extension background pages from being garbage collected.
const backgroundPages: Electron.WebContents[] = [];

app.on('web-contents-created', (e, webContents) => {
if (webContents.getType() === 'backgroundPage')
backgroundPages.push(webContents);
});
131 changes: 131 additions & 0 deletions src/main/menus/bookmarks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
Menu,
nativeImage,
NativeImage,
MenuItemConstructorOptions,
app,
} from 'electron';
import { join } from 'path';
import { IBookmark } from '~/interfaces';
import { Application } from '../application';
import { AppWindow } from '../windows/app';
import { showAddBookmarkDialog } from '../dialogs/add-bookmark';

function getPath(file: string) {
if (process.env.NODE_ENV === 'development') {
return join(
app.getAppPath(),
'src',
'renderer',
'resources',
'icons',
`${file}.png`,
);
} else {
const path = require(`~/renderer/resources/icons/${file}.png`);
return join(app.getAppPath(), `build`, path);
}
}

function getIcon(
favicon: string | undefined,
isFolder: boolean,
): NativeImage | string {
if (favicon) {
let dataURL = Application.instance.storage.favicons.get(favicon);
if (dataURL) {
// some favicon data urls have a corrupted base 64 file type descriptor
// prefixed with data:png;base64, instead of data:image/png;base64,
// see: https://github.com/electron/electron/issues/23369
if (!dataURL.split(',')[0].includes('image')) {
const split = dataURL.split(':');
dataURL = split.join(':image/');
}

const image = nativeImage
.createFromDataURL(dataURL)
.resize({ width: 16, height: 16 });
return image;
}
}

if (Application.instance.settings.object.theme === 'wexond-dark') {
if (isFolder) {
return getPath('folder_light');
} else {
return getPath('page_light');
}
} else {
if (isFolder) {
return getPath('folder_dark');
} else {
return getPath('page_dark');
}
}
}

export function createDropdown(
appWindow: AppWindow,
parentID: string,
bookmarks: IBookmark[],
): Electron.Menu {
const folderBookmarks = bookmarks.filter(
({ static: staticName, parent }) => !staticName && parent === parentID,
);
const template = folderBookmarks.map<MenuItemConstructorOptions>(
({ title, url, favicon, isFolder, _id }) => ({
click: url
? () => {
appWindow.viewManager.create({ url, active: true });
}
: undefined,
label: title,
icon: getIcon(favicon, isFolder),
submenu: isFolder ? createDropdown(appWindow, _id, bookmarks) : undefined,
id: _id,
}),
);

return template.length > 0
? Menu.buildFromTemplate(template)
: Menu.buildFromTemplate([{ label: '(empty)', enabled: false }]);
}

export function createMenu(appWindow: AppWindow, item: IBookmark) {
const { isFolder, url } = item;
const folderItems: MenuItemConstructorOptions[] = [
{
label: 'Open in New Tab',
click: () => {
appWindow.viewManager.create({ url, active: true });
},
},
{
type: 'separator',
},
];

const template: MenuItemConstructorOptions[] = [
...(!isFolder ? folderItems : []),
{
label: 'Edit',
click: () => {
const windowBounds = appWindow.win.getBounds();
showAddBookmarkDialog(appWindow.win, windowBounds.width - 20, 72, {
url: item.url,
title: item.title,
bookmark: item,
favicon: item.favicon,
});
},
},
{
label: 'Delete',
click: () => {
Application.instance.storage.removeBookmark(item._id);
},
},
];

return Menu.buildFromTemplate(template);
}
23 changes: 14 additions & 9 deletions src/main/menus/common-actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { extname } from 'path';
import { dialog } from 'electron';
import { windowsManager } from '..';
import { Application } from '../application';

export const saveAs = async () => {
const wc = windowsManager.currentWindow.viewManager.selected.webContents;
const {
title,
webContents,
} = Application.instance.windows.current.viewManager.selected;

const { canceled, filePath } = await dialog.showSaveDialog({
defaultPath: wc.getTitle(),
defaultPath: title,
filters: [
{ name: 'Webpage, Complete', extensions: ['html', 'htm'] },
{ name: 'Webpage, HTML Only', extensions: ['htm', 'html'] },
Expand All @@ -17,22 +20,24 @@ export const saveAs = async () => {

const ext = extname(filePath);

wc.savePage(filePath, ext === '.htm' ? 'HTMLOnly' : 'HTMLComplete');
webContents.savePage(filePath, ext === '.htm' ? 'HTMLOnly' : 'HTMLComplete');
};

export const viewSource = async () => {
const vm = windowsManager.currentWindow.viewManager;
const { viewManager } = Application.instance.windows.current;

vm.create(
viewManager.create(
{
url: `view-source:${vm.selected.webContents.getURL()}`,
url: `view-source:${viewManager.selected.url}`,
active: true,
},
true,
);
};

export const printPage = () => {
const wc = windowsManager.currentWindow.viewManager.selected.webContents;
wc.print();
const {
webContents,
} = Application.instance.windows.current.viewManager.selected;
webContents.print();
};
Loading