Repository: manusa/electronim (js)

Issue:
Black squares in Gmail
When using Gmail in Electronim there is a bunch of black/grey squares instead of icons

![image](https://user-images.githubusercontent.com/342110/189981719-e5895140-9182-4dd0-9b8c-10229159ca4e.png)
 
I played a little bit with the code and found it was the `google account` user agent rules that caused the issue. 

It causes gmail to fetch css rules targetted at firefox.

in `src/user-agent/index.js` 

```
 if (
          details.url.match(/https?:\/\/[^/]+google\.com.*/) && // NOSONAR
          !details.url.match(/https?:\/\/meet.google\.com.*/)
        ) {
```

If you replace it 
```
if (details.url.match(/https?:\/\/accounts.google\.com.*/)) {
```

It fix the issue and all google sign-in I could test still worked

I didn't create a pull-request only because I did not know if you had this rule for other things google related

Thanks for your great app.

Pull request:
fix: :bug: fix black squares in gmail
Fix #106 

Icons were displayed as black squares. the addUserAgentInterceptor was to large and I added a check for gmail

Before
![image](https://user-images.githubusercontent.com/342110/190903879-770df136-019f-4243-9b8b-bf1bf1725b11.png)

After
![image](https://user-images.githubusercontent.com/342110/190903903-ab2063ce-fc4e-4bc9-a496-4e7968c96501.png)

I confirm the sign in is still possible with no "browser security check"



Patch:
diff --git a/.eslintrc b/.eslintrc
index abfa58a..8081631 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -9,7 +9,7 @@
     "plugin:jest/recommended"
   ],
   "parserOptions": {
-    "ecmaVersion": 9,
+    "ecmaVersion": "latest",
     "ecmaFeatures": {
       "experimentalObjectRestSpread": true,
       "destructuring": true,
@@ -115,7 +115,7 @@
     "indent": [2, 2, {
       "SwitchCase": 1
     }],
-    "arrow-body-style": [2, "as-needed"],
+    "arrow-body-style": [2, "as-needed"], // Consider disabling this one: https://twitter.com/dan_abramov/status/1570426739591114755
     "arrow-parens": [2, "as-needed"],
     "arrow-spacing": 2,
     "brace-style": 2,
@@ -180,5 +180,13 @@
   },
   "plugins": [
     "jest"
-  ]
+  ],
+  "settings": {
+    "import/extensions": [".mjs", ".js"],
+    "import/resolver": {
+      "node": {
+        "extensions": [".mjs", ".js"]
+      }
+    }
+  }
 }
diff --git a/package.json b/package.json
index 19fb800..56804f3 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
     "utils"
   ],
   "scripts": {
-    "pretest": "eslint --ignore-path .gitignore . && node webpack.js",
+    "pretest": "eslint --ext .mjs,.js --ignore-path .gitignore . && node webpack.js",
     "test": "jest --coverage",
     "prestart": "node webpack.js",
     "start": "electron .",
diff --git a/src/components/README.md b/src/components/README.md
index 8c1b4c4..d9c50b6 100644
--- a/src/components/README.md
+++ b/src/components/README.md
@@ -1,6 +1,6 @@
 # Components
 
-This module contains the library of shared components to be used accross the application.
+This module contains the library of shared components to be used across the application.
 
 ## Structure
 
diff --git a/src/constants/index.js b/src/constants/index.js
index 6eb599b..8eec872 100644
--- a/src/constants/index.js
+++ b/src/constants/index.js
@@ -31,6 +31,7 @@ const APP_EVENTS = {
   closeDialog: 'closeDialog',
   desktopCapturerGetSources: 'desktopCapturerGetSources',
   dictionaryGetAvailable: 'dictionaryGetAvailable',
+  dictionaryGetAvailableNative: 'dictionaryGetAvailableNative',
   dictionaryGetEnabled: 'dictionaryGetEnabled',
   dictionaryGetMisspelled: 'dictionaryGetMisspelled',
   notificationClick: 'notificationClick',
diff --git a/src/main/index.js b/src/main/index.js
index 7e1c5fa..d3d4212 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -17,7 +17,9 @@ const {BrowserWindow, Notification, app, desktopCapturer, ipcMain: ipc} = requir
 const {APP_EVENTS} = require('../constants');
 const {TABS_CONTAINER_HEIGHT, initTabContainer} = require('../chrome-tabs');
 const {loadSettings, updateSettings, openSettingsDialog} = require('../settings');
-const {getAvailableDictionaries, loadDictionaries, getEnabledDictionaries} = require('../spell-check');
+const {
+  getAvailableDictionaries, getAvailableNativeDictionaries, loadDictionaries, getEnabledDictionaries
+} = require('../spell-check');
 const tabManager = require('../tab-manager');
 const {initBrowserVersions, userAgentForView} = require('../user-agent');
 
@@ -142,6 +144,7 @@ const saveSettings = (_event, settings) => {
 const initDialogListeners = () => {
   ipc.on(APP_EVENTS.closeDialog, closeDialog);
   ipc.handle(APP_EVENTS.dictionaryGetAvailable, getAvailableDictionaries);
+  ipc.handle(APP_EVENTS.dictionaryGetAvailableNative, getAvailableNativeDictionaries);
   ipc.handle(APP_EVENTS.dictionaryGetEnabled, getEnabledDictionaries);
   ipc.handle(APP_EVENTS.settingsLoad, loadSettings);
   ipc.on(APP_EVENTS.settingsSave, saveSettings);
diff --git a/src/settings/index.js b/src/settings/index.js
index 21673e9..1802d8f 100644
--- a/src/settings/index.js
+++ b/src/settings/index.js
@@ -21,7 +21,7 @@ const {showDialog} = require('../browser-window');
 
 const APP_DIR = '.electronim';
 const SETTINGS_FILE = 'settings.json';
-const DEFAULT_SETTINGS = {tabs: [], enabledDictionaries: ['en-US']};
+const DEFAULT_SETTINGS = {tabs: [], useNativeSpellChecker: false, enabledDictionaries: ['en-US']};
 
 const webPreferences = {
   contextIsolation: false,
diff --git a/src/settings/settings.browser.mjs b/src/settings/settings.browser.mjs
index db5d7e1..50f52d8 100644
--- a/src/settings/settings.browser.mjs
+++ b/src/settings/settings.browser.mjs
@@ -15,11 +15,17 @@
  */
 /* eslint-disable no-undef */
 const {
-  APP_EVENTS, ELECTRONIM_VERSION,  html, ipcRenderer, preact: {render}, preactHooks: {useReducer}, TopBar
+  APP_EVENTS, ELECTRONIM_VERSION, html, ipcRenderer, preact: {render}, preactHooks: {useReducer}, TopBar
 } = window;
 import {
-  ACTIONS, reducer, dictionariesEnabled, dictionariesAvailable, setTabProperty, toggleTabProperty
+  ACTIONS, reducer, dictionariesEnabled, setTabProperty, toggleTabProperty
 } from './settings.reducer.browser.mjs';
+import {
+  Checkbox
+} from './settings.common.browser.mjs';
+import {
+  SpellCheckContainer
+} from './settings.spell-check.browser.mjs';
 
 const settingsRoot = () => document.querySelector('.settings');
 
@@ -30,7 +36,6 @@ const newTabClass = state => {
   return state.newTabValid ? 'is-success' : 'is-danger';
 };
 
-
 const SettingsButton = ({icon, disabled = false, title, onclick}) => (html`
   <button class='settings__button button' disabled=${disabled} onclick=${onclick}>
     <span class='icon is-medium' title='${title}'>
@@ -50,15 +55,6 @@ const ExpandButton = ({dispatch, id, expanded = false}) => {
   `;
 };
 
-const Checkbox = ({label, title = '', icon, checked, value, onclick}) => (html`
-  <div class='control'>
-    <label class='checkbox' title=${title}>
-      <input type='checkbox' checked=${checked} title=${title} value=${value} onclick=${onclick} />
-      ${icon && html`<i class='checkbox__icon fas ${icon}'></i>`} ${label} 
-    </label>
-  </div>
-`);
-
 const disabledIcon = disabled => (disabled === true ? 'fa-eye-slash' : 'fa-eye');
 const sandboxedIcon = sandboxed => (sandboxed === true ? 'fa-lock' : 'fa-lock-open');
 
@@ -103,17 +99,10 @@ const TabEntry = ({
   </div>
 `);
 
-const DictionaryEntry = ({dispatch, dictionaryKey, name, enabled = false}) => (html`
-  <${Checkbox} label=${`${name} (${dictionaryKey})`} checked=${enabled}
-    value=${dictionaryKey}
-    onclick=${() => dispatch({type: ACTIONS.TOGGLE_DICTIONARY, payload: dictionaryKey})}
-  />
-`);
 
 const Settings = ({initialState}) => {
   const [state, dispatch] = useReducer(reducer, initialState);
   const enabledDictionaries = dictionariesEnabled(state);
-  const availableDictionaries = dictionariesAvailable(state);
   const onNewTabInput = ({target: {value}}) => dispatch({
     type: ACTIONS.UPDATE_NEW_TAB_VALUE,
     payload: value
@@ -127,6 +116,7 @@ const Settings = ({initialState}) => {
   const toggleNotifications = () => dispatch({type: ACTIONS.TOGGLE_GLOBAL_NOTIFICATIONS});
   const save = () => ipcRenderer.send(APP_EVENTS.settingsSave, {
     tabs: state.tabs,
+    useNativeSpellChecker: state.useNativeSpellChecker,
     enabledDictionaries,
     disableNotificationsGlobally: state.disableNotificationsGlobally
   });
@@ -170,20 +160,7 @@ const Settings = ({initialState}) => {
         `))}
         </div>
       </nav>
-      <nav class="panel">
-        <p class="panel-heading">Spell checker languages</p>
-        <div class="panel-block">
-          <div class="settings__dictionaries container">${
-  Object.entries(availableDictionaries)
-    .sort(([, {name: name1}], [, {name: name2}]) => name1.localeCompare(name2))
-    .map(([key, {name}]) => (html`
-      <${DictionaryEntry} dispatch=${dispatch} dictionaryKey=${key} name=${name}
-        enabled=${enabledDictionaries.includes(key)}
-      />
-    `))}
-          </div>
-        </div>
-      </nav>
+      <${SpellCheckContainer} dispatch=${dispatch} state=${state} />
       <nav class="panel">
         <p class="panel-heading">Other</p>
         <div class="panel-block">
@@ -209,20 +186,27 @@ const Settings = ({initialState}) => {
 Promise.all([
   ipcRenderer.invoke(APP_EVENTS.settingsLoad),
   ipcRenderer.invoke(APP_EVENTS.dictionaryGetAvailable),
+  ipcRenderer.invoke(APP_EVENTS.dictionaryGetAvailableNative),
   ipcRenderer.invoke(APP_EVENTS.dictionaryGetEnabled)
-]).then(([currentSettings, availableDictionaries, enabledDictionaries]) => {
-  const initialState = {
-    dictionaries: {
-      available: availableDictionaries,
-      enabled: enabledDictionaries
-    },
-    tabs: currentSettings.tabs,
-    expandedTabs: [],
-    newTabValid: false,
-    newTabValue: '',
-    invalidTabs: new Set(),
-    canSave: currentSettings.tabs.length > 0,
-    disableNotificationsGlobally: currentSettings.disableNotificationsGlobally
-  };
-  render(html`<${Settings} initialState=${initialState} />`, settingsRoot());
-});
+]).then(
+  (
+    [currentSettings, availableDictionaries, availableNativeDictionaries, enabledDictionaries]
+  ) => {
+    const initialState = {
+      dictionaries: {
+        available: availableDictionaries,
+        availableNative: availableNativeDictionaries,
+        enabled: enabledDictionaries
+      },
+      useNativeSpellChecker: currentSettings.useNativeSpellChecker,
+      tabs: currentSettings.tabs,
+      expandedTabs: [],
+      newTabValid: false,
+      newTabValue: '',
+      invalidTabs: new Set(),
+      canSave: currentSettings.tabs.length > 0,
+      disableNotificationsGlobally: currentSettings.disableNotificationsGlobally
+    };
+    render(html`<${Settings} initialState=${initialState} />`, settingsRoot());
+  }
+);
diff --git a/src/settings/settings.common.browser.mjs b/src/settings/settings.common.browser.mjs
new file mode 100644
index 0000000..0d17fdf
--- /dev/null
+++ b/src/settings/settings.common.browser.mjs
@@ -0,0 +1,51 @@
+/*
+   Copyright 2022 Marc Nuri San Felix
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ */
+const {html} = window;
+
+export const prependProtocol = url => {
+  if (url && !url.match(/^https?:\/\/.+/)) {
+    return `https://${url}`;
+  }
+  return url;
+};
+
+export const validateUrl = (url, allowNoProtocol = true) => {
+  if (allowNoProtocol) {
+    url = prependProtocol(url);
+  }
+  if (!url || !url.match(/^https?:\/\/.+/)) {
+    return false;
+  }
+  try {
+    return Boolean(new URL(url));
+  } catch (error) {
+    /* error is ignored */
+  }
+  return false;
+};
+
+export const newId = () => (
+  new Date().getTime().toString(36) + Math.random().toString(36).slice(2) // NOSONAR
+);
+
+export const Checkbox = ({label, title = '', icon, checked, value, onclick, ...properties}) => (html`
+  <div class='control'>
+    <label class='checkbox' title=${title}>
+      <input type='checkbox' checked=${checked} title=${title} value=${value} onclick=${onclick} ...${properties} />
+      ${icon && html`<i class='checkbox__icon fas ${icon}'></i>`} ${label}
+    </label>
+  </div>
+`);
diff --git a/src/settings/settings.reducer.browser.mjs b/src/settings/settings.reducer.browser.mjs
index 7a5e93a..e89286f 100644
--- a/src/settings/settings.reducer.browser.mjs
+++ b/src/settings/settings.reducer.browser.mjs
@@ -13,10 +13,13 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
+import {newId, prependProtocol, validateUrl} from './settings.common.browser.mjs';
+
 export const ACTIONS = {
   ADD: 'ADD',
   REMOVE: 'REMOVE',
   SET_TAB_PROPERTY: 'SET_TAB_PROPERTY',
+  TOGGLE_USE_NATIVE_SPELL_CHECKER: 'TOGGLE_USE_NATIVE_SPELL_CHECKER',
   TOGGLE_DICTIONARY: 'TOGGLE_DICTIONARY',
   TOGGLE_TAB_EXPANDED: 'TOGGLE_TAB_EXPANDED',
   TOGGLE_TAB_PROPERTY: 'TOGGLE_TAB_PROPERTY',
@@ -24,36 +27,23 @@ export const ACTIONS = {
   UPDATE_NEW_TAB_VALUE: 'UPDATE_NEW_TAB_VALUE'
 };
 
-const prependProtocol = url => {
-  if (url && !url.match(/^https?:\/\/.+/)) {
-    return `https://${url}`;
-  }
-  return url;
-};
-
-const validateUrl = (url, allowNoProtocol = true) => {
-  if (allowNoProtocol) {
-    url = prependProtocol(url);
-  }
-  if (!url || !url.match(/^https?:\/\/.+/)) {
-    return false;
-  }
-  try {
-    return Boolean(new URL(url));
-  } catch (error) {
-    /* error is ignored */
+// Selectors
+export const useNativeSpellChecker = state => state.useNativeSpellChecker;
+export const dictionariesEnabled = state => state.dictionaries.enabled;
+const dictionariesAvailableNative = state => state.dictionaries.availableNative;
+const dictionariesAvailable = state => state.dictionaries.available;
+export const dictionaries = state => {
+  if (useNativeSpellChecker(state)) {
+    return Object.entries(dictionariesAvailable(state))
+      .filter(([key]) => dictionariesAvailableNative(state).includes(key))
+      .reduce((acc, [key, value]) => {
+        acc[key] = value;
+        return acc;
+      }, {});
   }
-  return false;
+  return dictionariesAvailable(state);
 };
 
-const newId = () => (
-  new Date().getTime().toString(36) + Math.random().toString(36).slice(2) // NOSONAR
-);
-
-export const dictionariesEnabled = state => state.dictionaries.enabled;
-export const dictionariesAvailable = state => state.dictionaries.available;
-
-
 export const reducer = (state, action) => {
   switch (action.type) {
     case ACTIONS.ADD: {
@@ -95,6 +85,11 @@ export const reducer = (state, action) => {
       });
       return newState;
     }
+    case ACTIONS.TOGGLE_USE_NATIVE_SPELL_CHECKER: {
+      return {...state,
+        useNativeSpellChecker: !state.useNativeSpellChecker
+      };
+    }
     case ACTIONS.TOGGLE_DICTIONARY: {
       const newState = {...state};
       if (dictionariesEnabled(newState).includes(action.payload)) {
@@ -141,6 +136,11 @@ export const reducer = (state, action) => {
   }
 };
 
+// Action creators
+export const toggleUseNativeSpellChecker = ({dispatch}) =>
+  () => dispatch({type: ACTIONS.TOGGLE_USE_NATIVE_SPELL_CHECKER});
+export const toggleDictionary = ({dispatch, languageKey}) =>
+  () => dispatch({type: ACTIONS.TOGGLE_DICTIONARY, payload: languageKey});
 export const setTabProperty = ({dispatch, property, value, id}) =>
   dispatch({type: ACTIONS.SET_TAB_PROPERTY, payload: {id, property, value}});
 export const toggleTabProperty = (dispatch, property, id) =>
diff --git a/src/settings/settings.spell-check.browser.mjs b/src/settings/settings.spell-check.browser.mjs
new file mode 100644
index 0000000..219c12b
--- /dev/null
+++ b/src/settings/settings.spell-check.browser.mjs
@@ -0,0 +1,60 @@
+/*
+   Copyright 2022 Marc Nuri San Felix
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ */
+const {html} = window;
+
+import {
+  dictionaries,
+  dictionariesEnabled,
+  toggleUseNativeSpellChecker,
+  toggleDictionary,
+  useNativeSpellChecker
+} from './settings.reducer.browser.mjs';
+import {Checkbox} from './settings.common.browser.mjs';
+
+const LanguageEntry = ({dispatch, languageKey, name, enabled = false}) => html`
+  <${Checkbox} label=${`${name} (${languageKey})`} checked=${enabled}
+    value=${languageKey}
+    onclick=${toggleDictionary({dispatch, languageKey})}
+  />
+`;
+
+export const SpellCheckContainer = ({dispatch, state}) => {
+  const useNative = useNativeSpellChecker(state);
+  const enabledDictionaries = dictionariesEnabled(state);
+  return html`
+    <nav class="settings__spell-check panel">
+      <p class="panel-heading">Spell check</p>
+      <div class="settings__spell-check-common panel-block">
+        <${Checkbox}
+            label='Use Native Spell Checker' checked=${useNative} value=${useNative}
+            onclick="${toggleUseNativeSpellChecker({dispatch})}"
+            data-testid='use-native-spell-checker'
+        />
+      </div>
+      <div class="panel-block">
+        <div class="settings__dictionaries container">${
+  Object.entries(dictionaries(state))
+    .sort(([, {name: name1}], [, {name: name2}]) => name1.localeCompare(name2))
+    .map(([key, {name}]) => (html`
+      <${LanguageEntry} dispatch=${dispatch} languageKey=${key} name=${name}
+        enabled=${enabledDictionaries.includes(key)}
+      />
+    `))}
+        </div>
+      </div>
+    </nav>
+  `;
+};
diff --git a/src/spell-check/index.js b/src/spell-check/index.js
index 20586b0..b525e0e 100644
--- a/src/spell-check/index.js
+++ b/src/spell-check/index.js
@@ -13,7 +13,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
-const {BrowserWindow, ipcMain} = require('electron');
+const {BrowserWindow, MenuItem, ipcMain} = require('electron');
 const {APP_EVENTS} = require('../constants');
 const {loadSettings} = require('../settings');
 
@@ -81,46 +81,66 @@ let fakeRendererWorker;
 
 const getAvailableDictionaries = () => AVAILABLE_DICTIONARIES;
 
+const getAvailableNativeDictionaries = () =>
+  fakeRendererWorker?.webContents.session.availableSpellCheckerLanguages ?? [];
+
 const handleGetMisspelled = async (_event, words) =>
   fakeRendererWorker.webContents.executeJavaScript(`getMisspelled(${JSON.stringify(words)})`);
 
+const getUseNativeSpellChecker = () => loadSettings().useNativeSpellChecker;
+
 const getEnabledDictionaries = () => loadSettings().enabledDictionaries;
 
 const loadDictionaries = () => {
-  if (!fakeRendererWorker) {
-    fakeRendererWorker = new BrowserWindow({
-      show: false,
-      webPreferences: {
-        contextIsolation: false,
-        nativeWindowOpen: true,
-        nodeIntegration: true
-      }
-    });
-    ipcMain.handle(APP_EVENTS.dictionaryGetMisspelled, handleGetMisspelled);
+  if (fakeRendererWorker) {
+    fakeRendererWorker.destroy();
   }
+  fakeRendererWorker = new BrowserWindow({
+    show: false,
+    webPreferences: {
+      contextIsolation: false,
+      nativeWindowOpen: true,
+      nodeIntegration: true
+    }
+  });
   fakeRendererWorker.loadURL(`file://${__dirname}/dictionary.renderer/index.html`);
+  ipcMain.removeHandler(APP_EVENTS.dictionaryGetMisspelled);
+  ipcMain.handle(APP_EVENTS.dictionaryGetMisspelled, handleGetMisspelled);
   // Uncomment to debug problems with dictionaries
   // fakeRendererWorker.webContents.openDevTools();
 };
 
+const menuItem = ({webContents, suggestion}) => new MenuItem({
+  label: suggestion,
+  click: () => {
+    webContents.replaceMisspelling(suggestion);
+  }
+});
+
 const contextMenuHandler = async (_event, {misspelledWord}, webContents) => {
-  const {MenuItem} = require('electron');
   const ret = [];
   if (misspelledWord && misspelledWord.length > 0) {
     const suggestions = await fakeRendererWorker.webContents.executeJavaScript(`getSuggestions('${misspelledWord}')`);
-    suggestions.forEach(suggestion =>
-      ret.push(new MenuItem({
-        label: suggestion,
-        click: () => {
-          webContents.replaceMisspelling(suggestion);
-        }
-      }))
-    );
+    suggestions.forEach(suggestion => ret.push(menuItem({webContents, suggestion})));
   }
   return ret;
 };
 
+const contextMenuNativeHandler = (_event, {misspelledWord, dictionarySuggestions = []}, webContents) => {
+  const ret = [];
+  if (misspelledWord && misspelledWord.length > 0) {
+    dictionarySuggestions.forEach(suggestion => ret.push(menuItem({webContents, suggestion})));
+  }
+  return ret;
+};
 
 module.exports = {
-  AVAILABLE_DICTIONARIES, contextMenuHandler, getAvailableDictionaries, getEnabledDictionaries, loadDictionaries
+  AVAILABLE_DICTIONARIES,
+  contextMenuHandler,
+  contextMenuNativeHandler,
+  getAvailableDictionaries,
+  getAvailableNativeDictionaries,
+  getEnabledDictionaries,
+  getUseNativeSpellChecker,
+  loadDictionaries
 };
diff --git a/src/tab-manager/index.js b/src/tab-manager/index.js
index 99c61a9..11949b2 100644
--- a/src/tab-manager/index.js
+++ b/src/tab-manager/index.js
@@ -17,7 +17,7 @@ const {app, BrowserView, Menu, MenuItem, session} = require('electron');
 const path = require('path');
 const {APP_EVENTS} = require('../constants');
 const settings = require('../settings');
-const {contextMenuHandler} = require('../spell-check');
+const {contextMenuHandler, contextMenuNativeHandler, getEnabledDictionaries, getUseNativeSpellChecker} = require('../spell-check');
 const {userAgentForView, addUserAgentInterceptor} = require('../user-agent');
 const {handleRedirect} = require('./redirect');
 
@@ -58,8 +58,12 @@ const handlePageFaviconUpdated = (browserView, ipcSender, tabId) => async (_e, f
 const handleContextMenu = browserView => async (event, params) => {
   const {webContents} = browserView;
   const menu = new Menu();
-
-  const spellingSuggestions = await contextMenuHandler(event, params, webContents);
+  let spellingSuggestions;
+  if (webContents.session.spellcheck) {
+    spellingSuggestions = contextMenuNativeHandler(event, params, webContents);
+  } else {
+    spellingSuggestions = await contextMenuHandler(event, params, webContents);
+  }
   if (spellingSuggestions.length > 0) {
     spellingSuggestions.forEach(mi => menu.append(mi));
     menu.append(new MenuItem({type: 'separator'}));
@@ -79,6 +83,8 @@ const cleanUserAgent = browserView => {
 };
 
 const addTabs = ipcSender => tabsMetadata => {
+  const useNativeSpellChecker = getUseNativeSpellChecker();
+  const enabledDictionaries = getEnabledDictionaries();
   tabsMetadata.forEach(({id, url, sandboxed = false}) => {
     const tabPreferences = {...webPreferences};
     if (sandboxed) {
@@ -87,6 +93,19 @@ const addTabs = ipcSender => tabsMetadata => {
       tabPreferences.session = session.defaultSession;
     }
     addUserAgentInterceptor(tabPreferences.session);
+
+    tabPreferences.session.spellcheck = useNativeSpellChecker;
+    if (useNativeSpellChecker) {
+      tabPreferences.session.setSpellCheckerEnabled(true);
+      tabPreferences.session.setSpellCheckerLanguages(tabPreferences.session.availableSpellCheckerLanguages
+        .filter(lang => enabledDictionaries.includes(lang)));
+    }
+
+
+    tabPreferences.experiment = false;
+    if (tabPreferences.experiment) { // USE NATIVE SPELL CHECKER
+      tabPreferences.session.setSpellCheckerDictionaryDownloadURL('file:///home/user/00-MN/projects/manusa/electronim/dictionaries/');
+    }
     const tab = new BrowserView({webPreferences: tabPreferences});
     tab.setAutoResize({width: true, height: true});
 
diff --git a/src/tab-manager/preload.js b/src/tab-manager/preload.js
index 5a408da..b69f408 100644
--- a/src/tab-manager/preload.js
+++ b/src/tab-manager/preload.js
@@ -13,11 +13,10 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
-const {webFrame} = require('electron');
 require('./preload.notification-shim');
 require('./preload.mediadevices-shim');
-const {initKeyboardShortcuts} = require('./preload.keyboard-shortcuts');
-const {initSpellChecker} = require('./preload.spell-check');
+require('./preload.keyboard-shortcuts').initKeyboardShortcuts();
+require('./preload.spell-check').initSpellChecker()
+  // eslint-disable-next-line no-console
+  .catch(err => console.error('Error initializing spell check', err));
 
-initKeyboardShortcuts();
-initSpellChecker(webFrame);
diff --git a/src/tab-manager/preload.spell-check.js b/src/tab-manager/preload.spell-check.js
index 3257303..55ae083 100644
--- a/src/tab-manager/preload.spell-check.js
+++ b/src/tab-manager/preload.spell-check.js
@@ -13,7 +13,8 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
-const {ipcRenderer} = require('electron');
+/* eslint-disable no-undef */
+const {ipcRenderer, webFrame} = require('electron');
 
 const spellCheckFunction = async (words, callback) => {
   // eslint-disable-next-line no-undef
@@ -21,8 +22,15 @@ const spellCheckFunction = async (words, callback) => {
   callback(misspelled);
 };
 
-const initSpellChecker = webFrame => {
-  webFrame.setSpellCheckProvider(navigator.language, {spellCheck: spellCheckFunction});
-};
+const initSpellChecker = () => new Promise(resolve => {
+  ipcRenderer.invoke(APP_EVENTS.settingsLoad)
+    .then(settings => {
+      if (!settings.useNativeSpellChecker) {
+        webFrame.setSpellCheckProvider(navigator.language, {spellCheck: spellCheckFunction});
+      }
+      resolve();
+    })
+    .catch(initSpellChecker);
+});
 
 module.exports = {initSpellChecker};
diff --git a/src/user-agent/index.js b/src/user-agent/index.js
index 8e43c42..cf1f8f5 100644
--- a/src/user-agent/index.js
+++ b/src/user-agent/index.js
@@ -85,6 +85,7 @@ const addUserAgentInterceptor = session => {
       if (
         details.url.match(/https?:\/\/[^/]+google\.com.*/) // NOSONAR
         && !details.url.match(/https?:\/\/meet.google\.com.*/)
+        && !details.url.match(/https?:\/\/mail.google\.com.*/)
       ) {
         details.requestHeaders['User-Agent'] = firefoxUserAgent(details.requestHeaders['User-Agent'])(BROWSER_VERSIONS.firefoxESR);
       }


Test patch:
diff --git a/src/settings/__tests__/index.test.js b/src/settings/__tests__/index.test.js
index 08e2ca7..8bf5617 100644
--- a/src/settings/__tests__/index.test.js
+++ b/src/settings/__tests__/index.test.js
@@ -99,7 +99,7 @@ describe('Settings module test suite', () => {
       // Then
       expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
       expect(fs.writeFileSync).toHaveBeenCalledWith(path.join('$HOME', '.electronim', 'settings.json'),
-        '{\n  "tabs": [],\n  "enabledDictionaries": [\n    "en-US"\n  ]\n}');
+        '{\n  "tabs": [],\n  "useNativeSpellChecker": false,\n  "enabledDictionaries": [\n    "en-US"\n  ]\n}');
     });
     test('object and saved settings, should overwrite overlapping settings', () => {
       // Given
@@ -109,7 +109,9 @@ describe('Settings module test suite', () => {
       // Then
       expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
       expect(fs.writeFileSync).toHaveBeenCalledWith(path.join('$HOME', '.electronim', 'settings.json'),
-        '{\n  "tabs": [\n    {\n      "id": 1337\n    }\n  ],\n  "enabledDictionaries": [\n    "en-US"\n  ],\n' +
+        '{\n  "tabs": [\n    {\n      "id": 1337\n    }\n  ],\n' +
+        '  "useNativeSpellChecker": false,\n' +
+        '  "enabledDictionaries": [\n    "en-US"\n  ],\n' +
         '  "activeTab": 1337,\n  "otherSetting": "1337"\n}');
     });
     test('object and saved settings with activeTab removed, should update activeTab', () => {
@@ -120,7 +122,9 @@ describe('Settings module test suite', () => {
       // Then
       expect(fs.writeFileSync).toHaveBeenCalledTimes(1);
       expect(fs.writeFileSync).toHaveBeenCalledWith(path.join('$HOME', '.electronim', 'settings.json'),
-        '{\n  "tabs": [\n    {\n      "id": 1337\n    }\n  ],\n  "enabledDictionaries": [\n    "en-US"\n  ],\n' +
+        '{\n  "tabs": [\n    {\n      "id": 1337\n    }\n  ],\n' +
+        '  "useNativeSpellChecker": false,\n' +
+        '  "enabledDictionaries": [\n    "en-US"\n  ],\n' +
         '  "activeTab": 1337\n}');
     });
   });
diff --git a/src/settings/__tests__/settings.browser.test.mjs b/src/settings/__tests__/settings.browser.test.mjs
index a18b339..11c7738 100644
--- a/src/settings/__tests__/settings.browser.test.mjs
+++ b/src/settings/__tests__/settings.browser.test.mjs
@@ -25,12 +25,14 @@ const mockDOM = () => {
 };
 
 describe('Settings in Browser test suite', () => {
+  let mockDictionariesAvailableNative;
   let mockDictionariesAvailable;
   let mockDictionariesEnabled;
   let mockCurrentSettings;
   let mockIpcRenderer;
   beforeEach(async () => {
     jest.resetModules();
+    mockDictionariesAvailableNative = ['en'];
     mockDictionariesAvailable = {
       en: {name: 'English'},
       es: {name: 'Spanish'}
@@ -49,12 +51,15 @@ describe('Settings in Browser test suite', () => {
         switch (channel) {
           case 'settingsLoad':
             return mockCurrentSettings;
+          case 'dictionaryGetAvailableNative':
+            return mockDictionariesAvailableNative;
           case 'dictionaryGetAvailable':
             return mockDictionariesAvailable;
           case 'dictionaryGetEnabled':
             return mockDictionariesEnabled;
+          default:
+            return {};
         }
-        return {};
       })
     };
     window.preact = await import('preact');
@@ -348,28 +353,56 @@ describe('Settings in Browser test suite', () => {
       });
     });
   });
-  describe('Dictionary events', () => {
-    let $dictionaries;
+  describe('Spell Check events', () => {
+    let $spellCheckContainer;
     beforeEach(() => {
-      $dictionaries = document.querySelector('.settings__dictionaries');
+      $spellCheckContainer = document.querySelector('.settings__spell-check');
     });
-    test('toggle active dictionary, should uncheck dictionary', async () => {
+    test('toggle use native spell checker, should check use native spell checker', async () => {
       // Given
-      const $enDict = $dictionaries.querySelector('input[value=en]');
-      expect($enDict.checked).toBe(true);
+      const $useNativeSpellChecker = $spellCheckContainer
+        .querySelector('.settings__spell-check-common input[data-testid=use-native-spell-checker]');
+      expect($useNativeSpellChecker.checked).toBe(false);
       // When
-      fireEvent.click($enDict);
+      fireEvent.click($useNativeSpellChecker);
       // Then
-      waitFor(() => expect($enDict.checked).toBe(false));
+      await waitFor(() => expect($useNativeSpellChecker.checked).toBe(true));
     });
-    test('toggle inactive dictionary, should check dictionary', async () => {
-      // Given
-      const $esDict = $dictionaries.querySelector('input[value=es]');
-      expect($esDict.checked).toBe(false);
-      // When
-      fireEvent.click($esDict);
-      // Then
-      waitFor(() => expect($esDict.checked).toBe(true));
+    describe('dictionaries', () => {
+      let $dictionaries;
+      beforeEach(() => {
+        $dictionaries = $spellCheckContainer.querySelector('.settings__dictionaries');
+      });
+      test('toggle active dictionary, should uncheck dictionary', async () => {
+        // Given
+        const $enDict = $dictionaries.querySelector('input[value=en]');
+        expect($enDict.checked).toBe(true);
+        // When
+        fireEvent.click($enDict);
+        // Then
+        await waitFor(() => expect($enDict.checked).toBe(false));
+      });
+      test('toggle inactive dictionary, should check dictionary', async () => {
+        // Given
+        const $esDict = $dictionaries.querySelector('input[value=es]');
+        expect($esDict.checked).toBe(false);
+        // When
+        fireEvent.click($esDict);
+        // Then
+        await waitFor(() => expect($esDict.checked).toBe(true));
+      });
+      test('when not native, dictionaries should intersect available', async () => {
+        await waitFor(() => expect($dictionaries.querySelectorAll('input').length).toBe(2));
+      });
+      test('when native, dictionaries should intersect available', async () => {
+        // Given
+        const $useNativeSpellChecker = $spellCheckContainer
+          .querySelector('.settings__spell-check-common input[data-testid=use-native-spell-checker]');
+        // When
+        fireEvent.click($useNativeSpellChecker);
+        // Then
+        await waitFor(() => expect($dictionaries.querySelectorAll('input').length).toBe(1));
+      });
     });
   });
 });
diff --git a/src/spell-check/__tests__/index.test.js b/src/spell-check/__tests__/index.test.js
index dea62d3..3a4e2c0 100644
--- a/src/spell-check/__tests__/index.test.js
+++ b/src/spell-check/__tests__/index.test.js
@@ -19,19 +19,22 @@ describe('Spell-check module test suite', () => {
   let mockSettings;
   let spellCheck;
   beforeEach(() => {
+    jest.resetModules();
     mockBrowserWindow = {
+      destroy: jest.fn(),
       loadURL: jest.fn()
     };
     mockIpc = {
-      handle: jest.fn()
+      handle: jest.fn(),
+      removeHandler: jest.fn()
     };
     mockSettings = {
       enabledDictionaries: []
     };
-    jest.resetModules();
     jest.mock('electron', () => ({
       BrowserWindow: jest.fn(() => mockBrowserWindow),
-      ipcMain: mockIpc
+      ipcMain: mockIpc,
+      MenuItem: jest.fn(({label, click}) => ({label, click}))
     }));
     jest.mock('../../settings', () => ({
       loadSettings: jest.fn(() => mockSettings)
@@ -46,11 +49,115 @@ describe('Spell-check module test suite', () => {
     // Then
     expect(result).toEqual(['13-37']);
   });
-  test('loadDictionaries', () => {
-    // When
-    spellCheck.loadDictionaries();
-    // Then
-    expect(mockBrowserWindow.loadURL).toHaveBeenCalledWith(expect.stringMatching(/\/dictionary.renderer\/index.html$/));
-    expect(mockIpc.handle).toHaveBeenCalledWith('dictionaryGetMisspelled', expect.any(Function));
+  describe('loadDictionaries', () => {
+    test('should destroy existing previous fakeRenderer', () => {
+      // Given
+      spellCheck.loadDictionaries();
+      // When
+      spellCheck.loadDictionaries();
+      // Then
+      expect(mockBrowserWindow.destroy).toHaveBeenCalledTimes(1);
+    });
+    test('should not destroy non-existing previous fakeRenderer', () => {
+      // When
+      spellCheck.loadDictionaries();
+      // Then
+      expect(mockBrowserWindow.destroy).not.toHaveBeenCalled();
+    });
+    test('should remove and then add handler', () => {
+      // When
+      spellCheck.loadDictionaries();
+      // Then
+      expect(mockIpc.handle).toHaveBeenCalledAfter(mockIpc.removeHandler);
+    });
+    test('should remove dictionaryGetMisspelled handler', () => {
+      // When
+      spellCheck.loadDictionaries();
+      // Then
+      expect(mockIpc.removeHandler).toHaveBeenCalledWith('dictionaryGetMisspelled');
+    });
+    test('should handle dictionaryGetMisspelled', () => {
+      // When
+      spellCheck.loadDictionaries();
+      // Then
+      expect(mockIpc.handle).toHaveBeenCalledWith('dictionaryGetMisspelled', expect.any(Function));
+    });
+    test('should load dictionary.renderer URL', () => {
+      // When
+      spellCheck.loadDictionaries();
+      // Then
+      expect(mockBrowserWindow.loadURL)
+        .toHaveBeenCalledWith(expect.stringMatching(/\/dictionary.renderer\/index.html$/));
+    });
+  });
+  describe('Context Menu handlers', () => {
+    let params;
+    let webContents;
+    beforeEach(() => {
+      params = {};
+      webContents = {};
+      mockBrowserWindow.webContents = webContents;
+      spellCheck.loadDictionaries();
+    });
+    describe('contextMenuHandler', () => {
+      test('with no misspelled word, should return empty array', async () => {
+        // When
+        const result = await spellCheck.contextMenuHandler({}, params, webContents);
+        // Then
+        expect(result).toEqual([]);
+      });
+      describe('with misspelled word', () => {
+        beforeEach(() => {
+          params.misspelledWord = 'the-word';
+        });
+        test('and no suggestions, should return empty array', async () => {
+          // Given
+          mockBrowserWindow.webContents.executeJavaScript = jest.fn(async () => []);
+          // When
+          const result = await spellCheck.contextMenuHandler({}, params, webContents);
+          // Then
+          expect(result).toEqual([]);
+        });
+        test('and suggestions, should return array of MenuItems', async () => {
+          // Given
+          mockBrowserWindow.webContents.executeJavaScript = jest.fn(async () => ['the-suggestion']);
+          // When
+          const result = await spellCheck.contextMenuHandler({}, params, webContents);
+          // Then
+          expect(result).toEqual([
+            expect.objectContaining({label: 'the-suggestion'})
+          ]);
+        });
+      });
+    });
+    describe('contextMenuNativeHandler', () => {
+      test('with no misspelled word, should return empty array', () => {
+        // When
+        const result = spellCheck.contextMenuNativeHandler({}, params, webContents);
+        // Then
+        expect(result).toEqual([]);
+      });
+      describe('with misspelled word', () => {
+        beforeEach(() => {
+          params.misspelledWord = 'the-word';
+        });
+        test('and no suggestions, should return empty array', () => {
+          // When
+          const result = spellCheck.contextMenuNativeHandler({}, params, webContents);
+          // Then
+          expect(result).toEqual([]);
+        });
+        test('and suggestions, should return array of MenuItems', async () => {
+          // Given
+          params.dictionarySuggestions = ['the-suggestion'];
+          // When
+          const result = spellCheck.contextMenuNativeHandler({}, params, webContents);
+          // Then
+          expect(result).toEqual([
+            expect.objectContaining({label: 'the-suggestion'})
+          ]);
+        });
+      });
+    });
   });
 });
diff --git a/src/tab-manager/__tests__/index.test.js b/src/tab-manager/__tests__/index.test.js
index ba72b75..b56fb7c 100644
--- a/src/tab-manager/__tests__/index.test.js
+++ b/src/tab-manager/__tests__/index.test.js
@@ -25,6 +25,7 @@ describe('Tab Manager module test suite', () => {
     mockBrowserView = {
       setAutoResize: jest.fn(),
       webContents: {
+        session: {},
         executeJavaScript: jest.fn(async () => {}),
         on: jest.fn(),
         loadURL: jest.fn(),
@@ -198,7 +199,7 @@ describe('Tab Manager module test suite', () => {
           mockMenu.append = jest.fn();
           mockMenu.popup = jest.fn();
         });
-        test('No spelling suggestions, should open a Menu with DevTools entry', async () => {
+        test('should open a Menu with DevTools entry', async () => {
           // Given
           spellChecker.contextMenuHandler.mockImplementationOnce(() => []);
           // When
@@ -208,26 +209,55 @@ describe('Tab Manager module test suite', () => {
           expect(electron.MenuItem).toHaveBeenCalledTimes(1);
           expect(electron.MenuItem).toHaveBeenCalledWith(expect.objectContaining({label: 'DevTools'}));
           expect(mockMenu.append).toHaveBeenCalledTimes(1);
-          expect(mockMenu.popup).toHaveBeenCalledTimes(1);
-          expect(mockMenu.popup).toHaveBeenCalledWith({x: 13, y: 37});
         });
-        test('Spelling suggestions, should open a Menu with all suggestions, a sperator and DevTools entry', async () => {
+        test('should open a Menu at the specified location', async () => {
           // Given
-          spellChecker.contextMenuHandler.mockImplementationOnce(() => [
-            new electron.MenuItem({label: 'suggestion 1'}),
-            new electron.MenuItem({label: 'suggestion 2'})
-          ]);
+          spellChecker.contextMenuHandler.mockImplementationOnce(() => []);
           // When
           await events['context-menu'](new Event(''), {x: 13, y: 37});
           // Then
-          expect(electron.Menu).toHaveBeenCalledTimes(1);
-          expect(electron.MenuItem).toHaveBeenCalledTimes(4);
-          expect(electron.MenuItem).toHaveBeenCalledWith({type: 'separator'});
-          expect(electron.MenuItem).toHaveBeenCalledWith(expect.objectContaining({label: 'DevTools'}));
-          expect(mockMenu.append).toHaveBeenCalledTimes(4);
           expect(mockMenu.popup).toHaveBeenCalledTimes(1);
           expect(mockMenu.popup).toHaveBeenCalledWith({x: 13, y: 37});
         });
+        describe('with native spellcheck disabled', () => {
+          beforeEach(() => {
+            mockBrowserView.webContents.session.spellcheck = false;
+          });
+          test('Spelling suggestions, should open a Menu with all suggestions, a separator and DevTools entry', async () => {
+            // Given
+            spellChecker.contextMenuHandler.mockImplementationOnce(() => [
+              new electron.MenuItem({label: 'suggestion 1'}),
+              new electron.MenuItem({label: 'suggestion 2'})
+            ]);
+            // When
+            await events['context-menu'](new Event(''), {x: 13, y: 37});
+            // Then
+            expect(electron.Menu).toHaveBeenCalledTimes(1);
+            expect(electron.MenuItem).toHaveBeenCalledTimes(4);
+            expect(electron.MenuItem).toHaveBeenCalledWith({type: 'separator'});
+            expect(electron.MenuItem).toHaveBeenCalledWith(expect.objectContaining({label: 'DevTools'}));
+            expect(mockMenu.append).toHaveBeenCalledTimes(4);
+          });
+        });
+        describe('with native spellcheck enabled', () => {
+          beforeEach(() => {
+            mockBrowserView.webContents.session.spellcheck = true;
+          });
+          test('Spelling suggestions, should open a Menu with all suggestions, a separator and DevTools entry', async () => {
+            // Given
+            spellChecker.contextMenuNativeHandler.mockImplementationOnce(() => [
+              new electron.MenuItem({label: 'suggestion 1'})
+            ]);
+            // When
+            await events['context-menu'](new Event(''), {x: 13, y: 37});
+            // Then
+            expect(electron.Menu).toHaveBeenCalledTimes(1);
+            expect(electron.MenuItem).toHaveBeenCalledTimes(3);
+            expect(electron.MenuItem).toHaveBeenCalledWith({type: 'separator'});
+            expect(electron.MenuItem).toHaveBeenCalledWith(expect.objectContaining({label: 'DevTools'}));
+            expect(mockMenu.append).toHaveBeenCalledTimes(3);
+          });
+        });
       });
     });
   });
diff --git a/src/tab-manager/__tests__/preload.spell-check.test.js b/src/tab-manager/__tests__/preload.spell-check.test.js
index 4f50f9b..d9dec91 100644
--- a/src/tab-manager/__tests__/preload.spell-check.test.js
+++ b/src/tab-manager/__tests__/preload.spell-check.test.js
@@ -13,36 +13,74 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
+const {waitFor} = require('@testing-library/dom');
+
 describe('Browser Spell Check test suite', () => {
   let mockIpcRenderer;
+  let mockWebFrame;
   let browserSpellCheck;
   beforeEach(() => {
-    global.APP_EVENTS = {
-      dictionaryGetMisspelled: 'dictionaryGetMisspelled'
-    };
+    jest.resetModules();
+    global.APP_EVENTS = require('../../constants').APP_EVENTS;
     mockIpcRenderer = {
-      invoke: jest.fn()
+      invoke: jest.fn(async () => ({useNativeSpellChecker: false}))
+    };
+    mockWebFrame = {
+      setSpellCheckProvider: jest.fn()
     };
-    jest.resetModules();
     jest.mock('electron', () => ({
-      ipcRenderer: mockIpcRenderer
+      ipcRenderer: mockIpcRenderer,
+      webFrame: mockWebFrame
     }));
     browserSpellCheck = require('../preload.spell-check');
   });
-  test('initSpellChecker, should set spell checker in provided webFrame', async () => {
+  describe('initSpellChecker', () => {
+    test('not-native, should load settings and set SpellCheckProvider in webFrame for navigator language', async () => {
+      // Given
+      Object.defineProperty(navigator, 'language', {value: 'eo'});
+      // When
+      browserSpellCheck.initSpellChecker();
+      // Then
+      await waitFor(() => expect(mockWebFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1));
+      expect(mockWebFrame.setSpellCheckProvider).toHaveBeenCalledWith('eo', expect.any(Object));
+      expect(mockIpcRenderer.invoke).toHaveBeenCalledTimes(1);
+      expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('settingsLoad');
+    });
+    test('native, should load settings and skip processing', async () => {
+      // Given
+      mockIpcRenderer.invoke = jest.fn(async () => ({useNativeSpellChecker: true}));
+      // When
+      await browserSpellCheck.initSpellChecker();
+      // Then
+      expect(mockWebFrame.setSpellCheckProvider).not.toHaveBeenCalled();
+      expect(mockIpcRenderer.invoke).toHaveBeenCalledTimes(1);
+      expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('settingsLoad');
+    });
+    test('retries in case of failure', async () => {
+      // Given
+      mockIpcRenderer.invoke = jest.fn()
+        .mockImplementationOnce(async () => {
+          throw new Error('failed');
+        })
+        .mockImplementationOnce(async () => ({useNativeSpellChecker: false}));
+      // When
+      browserSpellCheck.initSpellChecker();
+      // Then
+      await waitFor(() => expect(mockWebFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1));
+      expect(mockIpcRenderer.invoke).toHaveBeenCalledTimes(2);
+      expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('settingsLoad');
+    });
+  });
+  test('spellCheck, should invoke dictionaryGetMisspelled and trigger callback', async () => {
     // Given
     const callback = jest.fn();
-    Object.defineProperty(navigator, 'language', {value: 'eo'});
-    const webFrame = {
-      setSpellCheckProvider: jest.fn()
-    };
-    browserSpellCheck.initSpellChecker(webFrame);
+    browserSpellCheck.initSpellChecker();
+    await waitFor(() => expect(mockWebFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1));
     // When
-    await webFrame.setSpellCheckProvider.mock.calls[0][1].spellCheck([], callback);
+    await mockWebFrame.setSpellCheckProvider.mock.calls[0][1].spellCheck([], callback);
     // Then
-    expect(webFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1);
-    expect(webFrame.setSpellCheckProvider).toHaveBeenCalledWith('eo', expect.any(Object));
-    expect(mockIpcRenderer.invoke).toHaveBeenCalledTimes(1);
+    expect(mockIpcRenderer.invoke).toHaveBeenCalledTimes(2);
+    expect(mockIpcRenderer.invoke).toHaveBeenCalledWith('dictionaryGetMisspelled', []);
     expect(callback).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/src/tab-manager/__tests__/preload.test.js b/src/tab-manager/__tests__/preload.test.js
index 9c106a4..04d96b6 100644
--- a/src/tab-manager/__tests__/preload.test.js
+++ b/src/tab-manager/__tests__/preload.test.js
@@ -13,11 +13,15 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
+/* eslint-disable no-console */
+const {waitFor} = require('@testing-library/dom');
+
 describe('Tab Manager Module preload test suite', () => {
   let mockElectron;
   beforeEach(() => {
     mockElectron = {
-      webFrame: {setSpellCheckProvider: jest.fn()}
+      webFrame: {setSpellCheckProvider: jest.fn()},
+      ipcRenderer: {invoke: async () => ({useNativeSpellChecker: false})}
     };
     jest.resetModules();
     jest.mock('electron', () => mockElectron);
@@ -31,29 +35,42 @@ describe('Tab Manager Module preload test suite', () => {
       jest.spyOn(require('../preload.keyboard-shortcuts'), 'initKeyboardShortcuts');
       jest.spyOn(require('../preload.spell-check'), 'initSpellChecker');
     });
-    test('adds required libraries', () => {
+    test('adds required libraries', async () => {
       // When
       require('../preload');
       // Then
       expect(window.Notification).toEqual(expect.any(Function));
       expect(window.navigator.mediaDevices.getDisplayMedia).toEqual(expect.any(Function));
       expect(require('../preload.keyboard-shortcuts').initKeyboardShortcuts).toHaveBeenCalledTimes(1);
-      expect(mockElectron.webFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1);
+      await waitFor(() => expect(mockElectron.webFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1));
       expect(require('../preload.spell-check').initSpellChecker).toHaveBeenCalledTimes(1);
-      expect(require('../preload.spell-check').initSpellChecker).toHaveBeenCalledWith(mockElectron.webFrame);
+    });
+    test('logs error if can\'t initialize spell checker', async () => {
+      // Given
+      jest.mock('../preload.spell-check', () => ({
+        initSpellChecker: async () => {
+          throw new Error('Error initializing spell checker');
+        }
+      }));
+      jest.spyOn(console, 'error').mockImplementationOnce(() => {});
+      // When
+      require('../preload');
+      // Then
+      await waitFor(() => expect(console.error).toHaveBeenCalledTimes(1));
+      expect(console.error).toHaveBeenCalledWith('Error initializing spell check', expect.any(Error));
     });
   });
   describe('preload.bundle', () => {
     beforeEach(() => {
       window.addEventListener = jest.fn();
     });
-    test('adds required libraries', () => {
+    test('adds required libraries', async () => {
       // When
       require('../../../bundles/tab-manager.preload');
       // Then
       expect(window.Notification).toEqual(expect.any(Function));
       expect(window.navigator.mediaDevices.getDisplayMedia).toEqual(expect.any(Function));
-      expect(mockElectron.webFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1);
+      await waitFor(() => expect(mockElectron.webFrame.setSpellCheckProvider).toHaveBeenCalledTimes(1));
       expect(window.addEventListener).toHaveBeenCalledTimes(2);
       expect(window.addEventListener).toHaveBeenCalledWith('keyup', expect.any(Function));
       expect(window.addEventListener).toHaveBeenCalledWith('load', expect.any(Function));