OSDN Git Service

v2.8.1
[serene/MyBrowser.git] / app / electron / WindowManager.js
index 72ec184..e79d9c0 100644 (file)
@@ -1,21 +1,33 @@
-const { app, ipcMain, protocol, session, BrowserWindow, BrowserView, Menu, nativeImage, clipboard, dialog, Notification } = require('electron');
+const { app, shell, ipcMain, protocol, session, BrowserWindow, BrowserView, Menu, nativeImage, clipboard, dialog, Notification } = require('electron');
 const path = require('path');
-const fs = require('fs');
-const url = require('url');
+const { parse, format } = require('url');
 const os = require('os');
+const https = require('https');
+const http = require('http');
 
-const localShortcut = require("electron-localshortcut");
+const pkg = require(`${app.getAppPath()}/package.json`);
+const protocolStr = 'flast';
+const fileProtocolStr = `${protocolStr}-file`;
+
+const { download } = require('electron-dl');
+const platform = require('electron-platform');
+const localShortcut = require('electron-localshortcut');
 
 const Config = require('electron-store');
 const config = new Config({
     defaults: {
         design: {
-            homeButton: false,
-            darkTheme: false,
+            isHomeButton: false,
+            isBookmarkBar: false,
+            isDarkTheme: false,
+            isCustomTitlebar: true,
             theme: 'default'
         },
         homePage: {
-            defaultPage: 'my://newtab',
+            isDefaultHomePage: true,
+            defaultPage: `${protocolStr}://home`,
+        },
+        searchEngine: {
             defaultEngine: 'Google',
             searchEngines: [
                 {
@@ -35,6 +47,14 @@ const config = new Config({
                     url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
                 },
                 {
+                    name: 'OCN',
+                    url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
+                },
+                {
+                    name: 'Baidu',
+                    url: 'https://www.baidu.com/s?wd=%s'
+                },
+                {
                     name: 'Google Translate',
                     url: 'https://translate.google.com/?text=%s'
                 },
@@ -49,12 +69,34 @@ const config = new Config({
                 {
                     name: 'GitHub',
                     url: 'https://github.com/search?q=%s'
+                },
+                {
+                    name: 'DuckDuckGo',
+                    url: 'https://duckduckgo.com/?q=%s'
+                },
+                {
+                    name: 'Yahoo',
+                    url: 'https://search.yahoo.com/search?p=%s'
+                },
+                {
+                    name: 'Amazon',
+                    url: 'https://www.amazon.co.jp/s?k=%s'
                 }
             ]
         },
-        adBlocker: true,
+        pageSettings: {
+            media: -1,
+            geolocation: -1,
+            notifications: -1,
+            midiSysex: -1,
+            pointerLock: -1,
+            fullscreen: 1,
+            openExternal: -1,
+        },
+        isAdBlock: true,
+        language: 'ja',
         window: {
-            isCustomTitlebar: true,
+            isCloseConfirm: true,
             isMaximized: false,
             bounds: {
                 width: 1100,
@@ -64,29 +106,48 @@ const config = new Config({
     },
 });
 
+const lang = require(`${app.getAppPath()}/langs/${config.get('language')}.js`);
+
 const Datastore = require('nedb');
 let db = {};
+db.pageSettings = new Datastore({
+    filename: path.join(app.getPath('userData'), 'Files', 'PageSettings.db'),
+    autoload: true,
+    timestampData: true
+});
 
-db.history = new Datastore({
+db.historys = new Datastore({
     filename: path.join(app.getPath('userData'), 'Files', 'History.db'),
     autoload: true,
     timestampData: true
 });
-db.bookmark = new Datastore({
-    filename: path.join(app.getPath('userData'), 'Files', 'Bookmark.db'),
+db.downloads = new Datastore({
+    filename: path.join(app.getPath('userData'), 'Files', 'Download.db'),
+    autoload: true,
+    timestampData: true
+});
+db.bookmarks = new Datastore({
+    filename: path.join(app.getPath('userData'), 'Files', 'Bookmarks.db'),
+    autoload: true,
+    timestampData: true
+});
+
+db.apps = new Datastore({
+    filename: path.join(app.getPath('userData'), 'Files', 'Apps.db'),
     autoload: true,
     timestampData: true
 });
 
-const { loadFilters, updateFilters, removeAds } = require('./AdBlocker');
+const { loadFilters, updateFilters, runAdblockService, removeAds } = require('./AdBlocker');
 
 let floatingWindows = [];
 let views = [];
-let tabCount = 0;
 
-getBaseWindow = (width = 1100, height = 680, minWidth = 320, minHeight = 200, x, y, frame = false) => {
+getBaseWindow = (width = 1100, height = 680, minWidth = 500, minHeight = 360, x, y, frame = false) => {
     return new BrowserWindow({
-        width, height, minWidth, minHeight, x, y, 'titleBarStyle': 'hidden', frame, fullscreenable: true,
+        width, height, minWidth, minHeight, x, y, titleBarStyle: 'hidden', frame, fullscreenable: true,
+        icon: `${__dirname}/static/app/icon.png`,
+        show: false,
         webPreferences: {
             nodeIntegration: true,
             webviewTag: true,
@@ -97,47 +158,230 @@ getBaseWindow = (width = 1100, height = 680, minWidth = 320, minHeight = 200, x,
     });
 }
 
-registerProtocols = () => {
-    protocol.isProtocolHandled('my', (handled) => {
-        console.log(handled);
+loadSessionAndProtocol = () => {
+    const ses = session.defaultSession;
+
+    setPermissionRequestHandler(ses, false);
+
+    protocol.isProtocolHandled(protocolStr, (handled) => {
         if (!handled) {
-            protocol.registerFileProtocol('my', (request, callback) => {
-                const parsed = url.parse(request.url);
+            protocol.registerFileProtocol(protocolStr, (request, callback) => {
+                const parsed = parse(request.url);
 
-                if (parsed.hostname.endsWith('.css') || parsed.hostname.endsWith('.js')) {
-                    return callback({
-                        path: path.join(app.getAppPath(), 'pages', parsed.hostname),
-                    });
-                } else {
-                    return callback({
-                        path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
-                    });
-                }
+                return callback({
+                    path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
+                });
             }, (error) => {
                 if (error) console.error('Failed to register protocol: ' + error);
             });
         }
     });
-}
 
-registerProtocolWithPrivateMode = (windowId) => {
-    session.fromPartition(windowId).protocol.registerFileProtocol('my', (request, callback) => {
-        const parsed = url.parse(request.url);
+    protocol.isProtocolHandled(fileProtocolStr, (handled) => {
+        if (!handled) {
+            protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
+                const parsed = parse(request.url);
 
-        if (parsed.hostname.endsWith('.css') || parsed.hostname.endsWith('.js')) {
-            return callback({
-                path: path.join(app.getAppPath(), 'pages', parsed.hostname),
-            });
-        } else {
-            return callback({
-                path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
+                return callback({
+                    path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
+                });
+            }, (error) => {
+                if (error) console.error('Failed to register protocol: ' + error);
             });
         }
+    });
+}
+
+loadSessionAndProtocolWithPrivateMode = (windowId) => {
+    const ses = session.fromPartition(windowId);
+    ses.setUserAgent(ses.getUserAgent().replace(/ Electron\/[0-9\.]*/g, '') + ' PrivMode');
+
+    setPermissionRequestHandler(ses, true);
+
+    ses.protocol.registerFileProtocol(protocolStr, (request, callback) => {
+        const parsed = parse(request.url);
+
+        return callback({
+            path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
+        });
+    }, (error) => {
+        if (error) console.error('Failed to register protocol: ' + error);
+    });
+
+    ses.protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
+        const parsed = parse(request.url);
+
+        return callback({
+            path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
+        });
     }, (error) => {
         if (error) console.error('Failed to register protocol: ' + error);
     });
 }
 
+setPermissionRequestHandler = (ses, isPrivate = false) => {
+    if (!isPrivate) {
+        ses.setPermissionRequestHandler((webContents, permission, callback) => {
+            const url = parse(webContents.getURL());
+
+            db.pageSettings.findOne({ origin: `${url.protocol}//${url.hostname}` }, (err, doc) => {
+                if (doc != undefined) {
+                    if (permission == 'media' && doc.media != undefined && doc.media > -1)
+                        return callback(doc.media === 0);
+                    if (permission == 'geolocation' && doc.geolocation != undefined && doc.geolocation > -1)
+                        return callback(doc.geolocation === 0);
+                    if (permission == 'notifications' && doc.notifications != undefined && doc.notifications > -1)
+                        return callback(doc.notifications === 0);
+                    if (permission == 'midiSysex' && doc.midiSysex != undefined && doc.midiSysex > -1)
+                        return callback(doc.midiSysex === 0);
+                    if (permission == 'pointerLock' && doc.pointerLock != undefined && doc.pointerLock > -1)
+                        return callback(doc.pointerLock === 0);
+                    if (permission == 'fullscreen' && doc.fullscreen != undefined && doc.fullscreen > -1)
+                        return callback(doc.fullscreen === 0);
+                    if (permission == 'openExternal' && doc.openExternal != undefined && doc.openExternal > -1)
+                        return callback(doc.openExternal === 0);
+                } else {
+                    if (config.get(`pageSettings.${permission}`) === null || config.get(`pageSettings.${permission}`) === -1) {
+                        if (Notification.isSupported()) {
+                            const notify = new Notification({
+                                icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
+                                title: `${url.protocol}//${url.hostname} が権限を要求しています。`,
+                                body: '詳細はここをクリックしてください。',
+                                silent: true
+                            });
+    
+                            notify.show();
+    
+                            notify.on('click', (e) => {
+                                dialog.showMessageBox({
+                                    type: 'info',
+                                    title: '権限の要求',
+                                    message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
+                                    detail: `要求内容: ${permission}`,
+                                    checkboxLabel: 'このサイトでは今後も同じ処理をする',
+                                    checkboxChecked: false,
+                                    noLink: true,
+                                    buttons: ['はい / Yes', 'いいえ / No'],
+                                    defaultId: 0,
+                                    cancelId: 1
+                                }, (res, checked) => {
+                                    if (checked) {
+                                        if (permission == 'media')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, media: res }, { upsert: true });
+                                        if (permission == 'geolocation')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, geolocation: res }, { upsert: true });
+                                        if (permission == 'notifications')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, notifications: res }, { upsert: true });
+                                        if (permission == 'midiSysex')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, midiSysex: res }, { upsert: true });
+                                        if (permission == 'pointerLock')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, pointerLock: res }, { upsert: true });
+                                        if (permission == 'fullscreen')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, fullscreen: res }, { upsert: true });
+                                        if (permission == 'openExternal')
+                                            db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, openExternal: res }, { upsert: true });
+                                    }
+                                    return callback(res === 0);
+                                });
+                            });
+                            notify.on('close', (e) => {
+                                return callback(false);
+                            });
+                        } else {
+                            dialog.showMessageBox({
+                                type: 'info',
+                                title: '権限の要求',
+                                message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
+                                detail: `要求内容: ${permission}`,
+                                checkboxLabel: 'このサイトでは今後も同じ処理をする',
+                                checkboxChecked: false,
+                                noLink: true,
+                                buttons: ['はい / Yes', 'いいえ / No'],
+                                defaultId: 0,
+                                cancelId: 1
+                            }, (res, checked) => {
+                                if (checked) {
+                                    if (permission == 'media')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, media: res }, { upsert: true });
+                                    if (permission == 'geolocation')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, geolocation: res }, { upsert: true });
+                                    if (permission == 'notifications')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, notifications: res }, { upsert: true });
+                                    if (permission == 'midiSysex')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, midiSysex: res }, { upsert: true });
+                                    if (permission == 'pointerLock')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, pointerLock: res }, { upsert: true });
+                                    if (permission == 'fullscreen')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, fullscreen: res }, { upsert: true });
+                                    if (permission == 'openExternal')
+                                        db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, openExternal: res }, { upsert: true });
+                                }
+                                return callback(res === 0);
+                            });
+                        }
+                    } else if (config.get(`pageSettings.${permission}`) === 0) {
+                        return callback(false);
+                    } else if (config.get(`pageSettings.${permission}`) === 1) {
+                        return callback(true);
+                    }
+                }
+            });
+        });
+    } else {
+        ses.setPermissionRequestHandler((webContents, permission, callback) => {
+            const url = parse(webContents.getURL());
+            
+            if (config.get(`pageSettings.${permission}`) === null || config.get(`pageSettings.${permission}`) === -1) {
+                if (Notification.isSupported()) {
+                    const notify = new Notification({
+                        icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
+                        title: `${url.protocol}//${url.hostname} が権限を要求しています。`,
+                        body: '詳細はここをクリックしてください。\nプライベート ウィンドウ',
+                        silent: true
+                    });
+    
+                    notify.show();
+    
+                    notify.on('click', (e) => {
+                        dialog.showMessageBox({
+                            type: 'info',
+                            title: '権限の要求',
+                            message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
+                            detail: `要求内容: ${permission}`,
+                            noLink: true,
+                            buttons: ['はい / Yes', 'いいえ / No'],
+                            defaultId: 0,
+                            cancelId: 1
+                        }, (res) => {
+                            return callback(res === 0);
+                        });
+                    });
+                    notify.on('close', (e) => {
+                        return callback(false);
+                    });
+                } else {
+                    dialog.showMessageBox({
+                        type: 'info',
+                        title: '権限の要求',
+                        message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
+                        detail: `要求内容: ${permission}`,
+                        noLink: true,
+                        buttons: ['はい / Yes', 'いいえ / No'],
+                        defaultId: 0,
+                        cancelId: 1
+                    }, (res) => {
+                        return callback(res === 0);
+                    });
+                }
+            } else if (config.get(`pageSettings.${permission}`) === 0) {
+                return callback(false);
+            } else if (config.get(`pageSettings.${permission}`) === 1) {
+                return callback(true);
+            }
+        });
+    }
+}
+
 module.exports = class WindowManager {
     constructor() {
         this.windows = new Map();
@@ -145,29 +389,81 @@ module.exports = class WindowManager {
         ipcMain.on('window-add', (e, args) => {
             this.addWindow(args.isPrivate);
         });
+        
+        ipcMain.on('window-fullScreen', (e, args) => {
+            this.addWindow(args.isPrivate);
+        });
+
+        ipcMain.on('window-fixBounds', (e, args) => {
+            this.windows.forEach((value, key) => {
+                this.fixBounds(key, (floatingWindows.indexOf(key) != -1));
+            })
+        });
+
+        ipcMain.on('window-change-settings', (e, args) => {
+            this.windows.forEach((value, key) => {
+                value.webContents.send('window-change-settings', {});
+            })
+        });
 
         ipcMain.on('update-filters', (e, args) => {
             updateFilters();
         });
 
         ipcMain.on('data-history-get', (e, args) => {
-            db.history.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
+            db.historys.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
                 e.sender.send('data-history-get', { historys: docs });
             });
         });
 
         ipcMain.on('data-history-clear', (e, args) => {
-            db.history.remove({}, { multi: true });
+            db.historys.remove({}, { multi: true });
+        });
+
+        ipcMain.on('data-downloads-get', (e, args) => {
+            db.downloads.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
+                e.sender.send('data-downloads-get', { downloads: docs });
+            });
+        });
+
+        ipcMain.on('data-downloads-clear', (e, args) => {
+            db.downloads.remove({}, { multi: true });
         });
 
-        ipcMain.on('data-bookmark-get', (e, args) => {
-            db.bookmark.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
-                e.sender.send('data-bookmark-get', { bookmarks: docs });
+        ipcMain.on('data-bookmarks-get', (e, args) => {
+            db.bookmarks.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
+                e.sender.send('data-bookmarks-get', { bookmarks: docs });
             });
         });
 
-        ipcMain.on('data-bookmark-clear', (e, args) => {
-            db.bookmark.remove({}, { multi: true });
+        ipcMain.on('data-bookmarks-clear', (e, args) => {
+            db.bookmarks.remove({}, { multi: true });
+        });
+
+        ipcMain.on('data-apps-add', (e, args) => {
+            db.apps.update({ id: args.id }, { id: args.id, name: args.name, description: args.description, url: args.url }, { upsert: true });
+
+            db.apps.find({}).sort({ createdAt: -1 }).exec();
+        });
+
+        ipcMain.on('data-apps-remove', (e, args) => {
+            db.apps.remove({ id: args.id }, {});
+        });
+
+        ipcMain.on('data-apps-get', (e, args) => {
+            db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
+                e.sender.send('data-apps-get', { apps: docs });
+            });
+        });
+
+        ipcMain.on('data-apps-is', (e, args) => {
+            db.apps.find({ id: args.id }).exec((err, docs) => {
+                e.sender.send('data-apps-is', { id: args.id, isInstalled: (docs.length > 0 ? true : false) });
+            });
+        });
+
+        ipcMain.on('data-apps-clear', (e, args) => {
+            db.apps.remove({}, { multi: true });
         });
 
         ipcMain.on('clear-browsing-data', () => {
@@ -191,23 +487,27 @@ module.exports = class WindowManager {
             });
 
             config.clear();
-            db.history.remove({}, { multi: true });
-            db.bookmark.remove({}, { multi: true });
+            db.pageSettings.remove({}, { multi: true });
+
+            db.historys.remove({}, { multi: true });
+            db.downloads.remove({}, { multi: true });
+            db.bookmarks.remove({}, { multi: true });
+            db.apps.remove({}, { multi: true });
         });
     }
 
     addWindow = (isPrivate = false) => {
-        registerProtocols();
+        loadSessionAndProtocol();
         loadFilters();
 
         const { width, height, x, y } = config.get('window.bounds');
-        const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 320, 200, x, y, !config.get('window.isCustomTitlebar'));
+        const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 500, 360, x, y, !config.get('design.isCustomTitlebar'));
 
-        const id = (!isPrivate ? window.id : `private-${window.id}`);
+        const id = (!isPrivate ? `window-${window.id}` : `private-${window.id}`);
 
         config.get('window.isMaximized') && window.maximize();
 
-        const startUrl = process.env.ELECTRON_START_URL || url.format({
+        const startUrl = process.env.ELECTRON_START_URL || format({
             pathname: path.join(__dirname, '/../build/index.html'), // 警告:このファイルを移動する場合ここの相対パスの指定に注意してください
             protocol: 'file:',
             slashes: true,
@@ -216,6 +516,37 @@ module.exports = class WindowManager {
 
         window.loadURL(startUrl);
 
+        window.once('ready-to-show', () => {
+            window.show();
+        });
+
+        window.on('closed', () => {
+            this.windows.delete(id);
+        });
+
+        window.on('close', (e) => {
+            delete views[id];
+
+            config.set('window.isMaximized', window.isMaximized());
+            config.set('window.bounds', window.getBounds());
+        });
+
+        window.on('focus', () => {
+            window.webContents.send(`window-focus-${id}`, {});
+        });
+        window.on('blur', () => {
+            window.webContents.send(`window-blur-${id}`, {});
+        });
+
+        window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
+        window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
+        window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
+        window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
+        window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
+        window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
+
+        this.registerListeners(id);
+
         localShortcut.register(window, 'CmdOrCtrl+Shift+I', () => {
             if (window.getBrowserView() == undefined) return;
             const view = window.getBrowserView();
@@ -241,152 +572,161 @@ module.exports = class WindowManager {
             view.webContents.reloadIgnoringCache();
         });
 
-        window.on('closed', () => {
-            this.windows.delete(id);
-        });
-
-        ['resize', 'move'].forEach(ev => {
-            window.on(ev, () => {
-                config.set('window.isMaximized', window.isMaximized());
-                config.set('window.bounds', window.getBounds());
-            })
-        });
-
-        window.on('close', (e) => {
-            for (var i = 0; i < views.length; i++) {
-                if (views[i].windowId == id) {
-                    views.splice(i, 1);
-                }
-            }
-        });
-
-        window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
-        window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
-        window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
-        window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
-        window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
-        window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
-
-        // registerProtocols();
-        this.registerListeners(id);
+        localShortcut.register(window, 'F11', () => {
+            const window = this.windows.get(id);
+            
+            window.setFullScreen(!window.isFullScreen());
+            this.fixBounds(id, (floatingWindows.indexOf(id) != -1));
+        })
 
         this.windows.set(id, window);
+
+        if (process.argv != undefined) {
+            window.webContents.send(`tab-add-${id}`, { url: process.argv[process.argv.length - 1] });
+        }
     }
 
     registerListeners = (id) => {
-        ipcMain.on(`browserview-add-${id}`, (e, args) => {
+        ipcMain.on(`window-fullScreen-${id}`, (e, args) => {
+            const window = this.windows.get(id);
+            
+            window.setFullScreen(!window.isFullScreen());
+            this.fixBounds(id, (floatingWindows.indexOf(id) != -1));
+        });
+
+        ipcMain.on(`browserView-add-${id}`, (e, args) => {
             this.addView(id, args.url, args.isActive);
         });
 
-        ipcMain.on(`browserview-remove-${id}`, (e, args) => {
+        ipcMain.on(`browserView-remove-${id}`, (e, args) => {
             this.removeView(id, args.id);
         });
 
-        ipcMain.on(`browserview-select-${id}`, (e, args) => {
+        ipcMain.on(`browserView-select-${id}`, (e, args) => {
             this.selectView(id, args.id);
         });
 
-        ipcMain.on(`browserview-get-${id}`, (e, args) => {
+        ipcMain.on(`browserView-get-${id}`, (e, args) => {
             let datas = [];
-            for (var i = 0; i < views.length; i++) {
-                if (views[i].windowId == id) {
-                    const url = views[i].view.webContents.getURL();
-                    datas.push({ id: views[i].id, title: views[i].view.webContents.getTitle(), url: url, icon: url.startsWith('my://') ? undefined : `http://www.google.com/s2/favicons?domain=${url}` });
-                }
-            }
-            e.sender.send(`browserview-get-${id}`, { views: datas });
+
+            views[id].map((item) => {
+                const url = item.view.webContents.getURL();
+
+                datas.push({ id: item.view.webContents.id, title: item.view.webContents.getTitle(), url, icon: this.getFavicon(url), color: '#0a84ff', isBookmarked: false });
+            });
+            e.sender.send(`browserView-get-${id}`, { views: datas });
         });
 
-        ipcMain.on(`browserview-goBack-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
+        ipcMain.on(`browserView-goBack-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
                     if (webContents.canGoBack())
                         webContents.goBack();
                 }
             });
         });
 
-        ipcMain.on(`browserview-goForward-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
+        ipcMain.on(`browserView-goForward-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
                     if (webContents.canGoForward())
                         webContents.goForward();
                 }
             });
         });
 
-        ipcMain.on(`browserview-reload-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
+        ipcMain.on(`browserView-reload-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
                     webContents.reload();
                 }
             });
         });
 
-        ipcMain.on(`browserview-stop-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
+        ipcMain.on(`browserView-stop-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
                     webContents.stop();
                 }
             });
         });
 
-        ipcMain.on(`browserview-goHome-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
-                    webContents.loadURL(config.get('homePage.defaultPage'));
+        ipcMain.on(`browserView-goHome-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
+                    webContents.loadURL(config.get('homePage.isDefaultHomePage') ? `${protocolStr}://home/` : config.get('homePage.defaultPage'));
                 }
             });
         });
 
-        ipcMain.on(`browserview-loadURL-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
+        ipcMain.on(`browserView-loadURL-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
                     webContents.loadURL(args.url);
                 }
             });
         });
 
-        ipcMain.on(`browserview-loadFile-${id}`, (e, args) => {
-            views.filter(function (view, i) {
-                if (view.id == args.id) {
-                    let webContents = views[i].view.webContents;
+        ipcMain.on(`browserView-loadFile-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
                     webContents.loadFile(args.url);
                 }
             });
         });
 
+        ipcMain.on(`browserView-zoomIn-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
+                    console.log(webContents.getZoomFactor());
+                    webContents.setZoomFactor(webContents.getZoomFactor() + 0.1);
+                }
+            });
+        });
+
+        ipcMain.on(`browserView-zoomOut-${id}`, (e, args) => {
+            views[id].filter(function (view, i) {
+                if (view.view.webContents.id == args.id) {
+                    let webContents = views[id][i].view.webContents;
+                    console.log(webContents.getZoomFactor());
+                    webContents.setZoomFactor(webContents.getZoomFactor() - 0.1);
+                }
+            });
+        });
+
         ipcMain.on(`data-bookmark-add-${id}`, (e, args) => {
-            views.filter((view, i) => {
-                if (view.id == args.id) {
-                    let v = views[i].view;
-                    db.bookmark.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
-                    this.updateBookmarkState(id, args.id, v);
+            views[id].filter((view, i) => {
+                if (view.view.webContents.id == args.id) {
+                    let v = views[id][i].view;
+                    db.bookmarks.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
+                    this.updateViewState(id, v);
                 }
             });
         });
 
         ipcMain.on(`data-bookmark-remove-${id}`, (e, args) => {
-            views.filter((view, i) => {
-                if (view.id == args.id) {
-                    let v = views[i].view;
-                    db.bookmark.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
-                    this.updateBookmarkState(id, args.id, v);
+            views[id].filter((view, i) => {
+                if (view.view.webContents.id == args.id) {
+                    let v = views[id][i].view;
+                    db.bookmarks.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
+                    this.updateViewState(id, v);
                 }
             });
         });
 
         ipcMain.on(`data-bookmark-has-${id}`, (e, args) => {
-            views.filter((view, i) => {
-                if (view.id == args.id) {
-                    let v = views[i].view;
-                    db.bookmark.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
+            views[id].filter((view, i) => {
+                if (view.view.webContents.id == args.id) {
+                    let v = views[id][i].view;
+                    db.bookmarks.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
                         e.sender.send(`data-bookmark-has-${id}`, { isBookmarked: (docs.length > 0 ? true : false) });
                     });
                 }
@@ -394,22 +734,49 @@ module.exports = class WindowManager {
         });
     }
 
-    updateNavigationState = (windowId, id, view) => {
-        const window = this.windows.get(windowId);
-        window.webContents.send(`update-navigation-state-${windowId}`, {
-            id: id,
+    getFavicon = (url) => {
+        const parsed = parse(url);
+        return url.startsWith(`${protocolStr}://`) || url.startsWith(`${fileProtocolStr}://`) ? undefined : `https://www.google.com/s2/favicons?domain=${parsed.protocol}//${parsed.hostname}`;
+    }
+
+    updateNavigationState = (id, view) => {
+        const window = this.windows.get(id);
+        window.webContents.send(`update-navigation-state-${id}`, {
+            id: view.webContents.id,
             canGoBack: view.webContents.canGoBack(),
             canGoForward: view.webContents.canGoForward(),
         });
     }
 
-    updateBookmarkState = (windowId, id, view) => {
-        const window = this.windows.get(windowId);
-        db.bookmark.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
-            window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), isBookmarked: (docs.length > 0 ? true : false) });
+    updateViewState = (id, view) => {
+        const window = this.windows.get(id);
+        db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(id).startsWith('private')) }, (err, docs) => {
+            const url = view.webContents.getURL();
+
+            window.webContents.send(`browserView-load-${id}`, { id: view.webContents.id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), color: '#0a84ff', isAudioPlaying: !view.webContents.isCurrentlyAudible(), isBookmarked: (docs.length > 0 ? true : false) });
         });
     }
 
+    getColor = (view) => {
+        return new Promise((resolve, reject) => {
+            if (view !== null && !view.isDestroyed() && view.webContents !== null) {
+                view.webContents.executeJavaScript(
+                    `(function () {
+                        const heads = document.head.children;
+                        for (var i = 0; i < heads.length; i++) {
+                            if (heads[i].getAttribute('name') === 'theme-color') {
+                                return heads[i].getAttribute('content');
+                            }
+                        } 
+                    })()`, false, async (result) => {
+                        resolve(result !== null ? result : '#0a84ff');
+                    });
+            } else {
+                reject(new Error('WebContents are not available'));
+            }
+        });
+    };
+
     fixBounds = (windowId, isFloating = false) => {
         const window = this.windows.get(windowId);
 
@@ -418,6 +785,9 @@ module.exports = class WindowManager {
 
         const { width, height } = window.getContentBounds();
 
+        const baseBarHeight = 73;
+        const bookMarkBarHeight = 28;
+
         view.setAutoResize({ width: true, height: true });
         if (isFloating) {
             window.setMinimizable(false);
@@ -444,56 +814,136 @@ module.exports = class WindowManager {
                 });
             } else {
                 view.setBounds({
-                    x: 1,
-                    y: 73 + 1,
-                    width: width - 2,
-                    height: window.isMaximized() ? height - 73 : (height - 73) - 2,
+                    x: config.get('design.isCustomTitlebar') ? 1 : 0,
+                    y: config.get('design.isCustomTitlebar') ? this.getHeight(true, height, baseBarHeight, bookMarkBarHeight) + 1 : this.getHeight(true, height, baseBarHeight, bookMarkBarHeight),
+                    width: config.get('design.isCustomTitlebar') ? width - 2 : width,
+                    height: window.isMaximized() ? this.getHeight(false, height, baseBarHeight, bookMarkBarHeight) : (config.get('design.isCustomTitlebar') ? (this.getHeight(false, height, baseBarHeight, bookMarkBarHeight)) - 2 : (this.getHeight(false, height, baseBarHeight, bookMarkBarHeight)) - 1),
                 });
             }
         }
         view.setAutoResize({ width: true, height: true });
     }
 
-    addView = (windowId, url, isActive) => {
-        const id = tabCount++;
-        this.addTab(windowId, id, url, isActive);
+    getDomain = (url) => {
+        let hostname = url;
+
+        if (hostname.indexOf('http://') !== -1 || hostname.indexOf('https://') !== -1) {
+            hostname = hostname.split('://')[1];
+        }
+
+        if (hostname.indexOf('?') !== -1) {
+            hostname = hostname.split('?')[0];
+        }
+
+        if (hostname.indexOf('://') !== -1) {
+            hostname = `${hostname.split('://')[0]}://${hostname.split('/')[2]}`;
+        } else {
+            hostname = hostname.split('/')[0];
+        }
+
+        return hostname;
+    }
+
+    getCertificate = (url) => {
+        return new Promise((resolve, reject) => {
+            if (url.startsWith('http://')) return resolve({ type: 'InSecure' });
+            else if (url.startsWith(`${protocolStr}://`) || url.startsWith(`${fileProtocolStr}://`)) return resolve({ type: 'Internal' });
+
+            const domain = this.getDomain(url);
+
+            let options = {
+                host: domain,
+                port: 443,
+                method: 'GET'
+            };
+
+            let req = https.request(options, (res) => {
+                let certificate = res.connection.getPeerCertificate();
+                if (certificate.subject == null) return;
+
+                const data = {
+                    type: 'Secure',
+                    title: certificate.subject.O,
+                    country: certificate.subject.C
+                };
+                resolve(data);
+            });
+
+            req.end();
+        });
+    }
+
+    getHeight = (b, height, baseBarHeight, bookMarkBarHeight) => {
+        if (b) {
+            return config.get('design.isBookmarkBar') ? (baseBarHeight + bookMarkBarHeight) : baseBarHeight;
+        } else {
+            return height - (config.get('design.isBookmarkBar') ? (baseBarHeight + bookMarkBarHeight) : baseBarHeight);
+        }
+    };
+
+    addView = (id, url, isActive) => {
+        if (String(id).startsWith('private')) {
+            loadSessionAndProtocolWithPrivateMode(id);
+        }
+
+        this.addTab(id, url, isActive);
     }
 
     removeView = (windowId, id) => {
-        views.filter((view, i) => {
-            if (windowId == view.windowId && id == view.id) {
-                views.splice(i, 1);
+        views[windowId].filter((view, i) => {
+            if (view.view.webContents.id == id) {
+                const index = i;
+
+                if (index + 1 < views[windowId].length) {
+                    this.selectView2(windowId, index + 1);
+                } else if (index - 1 >= 0) {
+                    this.selectView2(windowId, index - 1);
+                }
+
+                views[windowId][index].view.destroy();
+                views[windowId].splice(index, 1);
             }
         });
     }
 
     selectView = (windowId, id) => {
         const window = this.windows.get(windowId);
-        views.filter((view, i) => {
-            if (windowId == view.windowId && id == view.id) {
-                window.setBrowserView(views[i].view);
-                window.setTitle(views[i].view.webContents.getTitle());
-                window.webContents.send(`browserview-set-${windowId}`, { id: id });
+        views[windowId].filter((item, i) => {
+            if (id == item.view.webContents.id) {
+                window.setBrowserView(item.view);
+                window.setTitle(`${item.view.webContents.getTitle()} - ${pkg.name}`);
+                
+                this.updateNavigationState(windowId, item.view);
+                this.updateViewState(windowId, item.view);
+
+                window.webContents.send(`browserView-set-${windowId}`, { id: id });
                 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
             }
         });
     }
 
+    selectView2 = (windowId, i) => {
+        const window = this.windows.get(windowId);
+        const item = views[windowId][i];
+
+        window.setBrowserView(item.view);
+        window.setTitle(`${item.view.webContents.getTitle()} - ${pkg.name}`);
+        window.webContents.send(`browserView-set-${windowId}`, { id: item.id });
+        this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
+    }
+
     getViews = (windowId) => {
         let datas = [];
-        for (var i = 0; i < views.length; i++) {
-            if (views[i].windowId == windowId) {
-                const url = views[i].view.webContents.getURL();
-                datas.push({ id: views[i].id, title: views[i].view.webContents.getTitle(), url: url, icon: url.startsWith('my://') ? undefined : `http://www.google.com/s2/favicons?domain=${url}` });
-            }
+        for (var i = 0; i < views[windowId].length; i++) {
+            const url = views[windowId][i].view.webContents.getURL();
+
+            datas.push({ id: views[windowId][i].view.webContents.id, title: views[windowId][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
         }
         const window = this.windows.get(windowId);
-        window.webContents.send(`browserview-get-${windowId}`, { views: datas });
+        window.webContents.send(`browserView-get-${windowId}`, { views: datas });
     }
 
-    addTab = (windowId, id, url = config.get('homePage.defaultPage'), isActive = true) => {
-        const window = this.windows.get(windowId);
-
+    addTab = (windowId, url = config.get('homePage.defaultPage'), isActive = true) => {
         const view = new BrowserView({
             webPreferences: {
                 nodeIntegration: false,
@@ -507,78 +957,140 @@ module.exports = class WindowManager {
             }
         });
 
-        const defaultUserAgent = view.webContents.getUserAgent();
+        view.webContents.setVisualZoomLevelLimits(1, 3);
 
-        if (String(windowId).startsWith('private')) {
-            registerProtocolWithPrivateMode(windowId);
-        }
+        const window = this.windows.get(windowId);
+        const id = view.webContents.id;
+
+        const executeJs = this.getRandString(12);
+        let viewId = '';
+
+        runAdblockService(window, windowId, id, view.webContents.session);
 
         view.webContents.on('did-start-loading', () => {
             if (view.isDestroyed()) return;
 
-            window.webContents.send(`browserview-start-loading-${windowId}`, { id: id });
+            window.webContents.send(`browserView-start-loading-${windowId}`, { id: id });
         });
         view.webContents.on('did-stop-loading', () => {
             if (view.isDestroyed()) return;
 
-            window.webContents.send(`browserview-stop-loading-${windowId}`, { id: id });
+            window.webContents.send(`browserView-stop-loading-${windowId}`, { id: id });
         });
 
-        view.webContents.on('did-finish-load', (e) => {
+        view.webContents.on('did-start-navigation', (e) => {
             if (view.isDestroyed()) return;
 
-            if (String(windowId).startsWith('private'))
-                view.webContents.setUserAgent(defaultUserAgent + ' PrivMode');
+            const url = view.webContents.getURL();
 
-            window.setTitle(view.webContents.getTitle());
-            this.updateBookmarkState(windowId, id, view);
+            /*
+            view.webContents.executeJavaScript(
+                `(function () {
+                    if (document.getElementById('tip-${executeJs}') === null) {
+                        let ${executeJs} = document.createElement('span');
+                        document.body.insertBefore(${executeJs}, document.body.firstChild);
+    
+                        ${executeJs}.id = 'tip-${executeJs}';
+                        ${executeJs}.style.display = 'none';
+                        ${executeJs}.style.position = 'fixed';
+                        ${executeJs}.style.bottom = 0;
+                        ${executeJs}.style.left = 0;
+                        ${executeJs}.style.zIndex = 999999;
+                        ${executeJs}.style.padding = '2px';
+                        ${executeJs}.style.userSelect = 'none';
+                        ${executeJs}.style.backgroundColor = '#f9f9fa';
+                        ${executeJs}.style.color = '#c7c7c7';
+                        ${executeJs}.style.borderTop = 'solid 1px #c7c7c7';
+                        ${executeJs}.style.borderRight = 'solid 1px #c7c7c7';
+                        ${executeJs}.style.fontSize = '12px';
+                        document.getElementById('tip-${executeJs}').style.fontFamily = '"Lato","Helvetica Neue",Helvetica,Arial,sans-serif';
+                    }
+                })()`
+            );
+            */
 
-            this.updateNavigationState(windowId, id, view);
-        });
+            if (config.get('isAdBlock') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
+                removeAds(url, view.webContents);
 
-        view.webContents.on('did-start-navigation', (e) => {
+            this.updateNavigationState(windowId, view);
+        });
+        view.webContents.on('did-finish-load', (e) => {
             if (view.isDestroyed()) return;
 
-            const url = view.webContents.getURL();
+            viewId = this.getRandString(12);
 
-            if (config.get('adBlocker'))
-                removeAds(url, view.webContents);
+            this.getCertificate(view.webContents.getURL()).then((certificate) => {
+                window.webContents.send(`browserView-certificate-${windowId}`, { id, certificate });
+            });
 
-            this.updateNavigationState(windowId, id, view);
+            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
+
+            this.updateViewState(windowId, view);
+            this.updateNavigationState(windowId, view);
+        });
+        view.webContents.on('did-fail-load', (e, code, description, url, isMainFrame, processId, routingId) => {
+            if (view.isDestroyed() || !isMainFrame || code === -3) return;
+
+            dialog.showMessageBox({ message: `${code}: ${description}` });
         });
 
         view.webContents.on('page-title-updated', (e) => {
             if (view.isDestroyed()) return;
 
-            window.setTitle(view.webContents.getTitle());
-            this.updateBookmarkState(windowId, id, view);
+            if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
+                db.historys.update({ id: viewId }, { id: viewId, title: view.webContents.getTitle(), url: view.webContents.getURL() }, { upsert: true });
 
-            if (!String(windowId).startsWith('private') && !view.webContents.getURL().startsWith('my://'))
-                db.history.insert({ title: view.webContents.getTitle(), url: view.webContents.getURL() });
+            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
 
-            this.updateNavigationState(windowId, id, view);
+            this.updateViewState(windowId, view);
+            this.updateNavigationState(windowId, view);
         });
-
         view.webContents.on('page-favicon-updated', (e, favicons) => {
             if (view.isDestroyed()) return;
 
-            window.setTitle(view.webContents.getTitle());
-            db.bookmark.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
-                window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), favicon: favicons[0], isBookmarked: (docs.length > 0 ? true : false) });
-            });
+            if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
+                db.historys.update({ id: viewId }, { id: viewId, title: view.webContents.getTitle(), url: view.webContents.getURL() }, { upsert: true });
 
-            this.updateNavigationState(windowId, id, view);
-        });
+            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
 
+            this.updateViewState(windowId, view);
+            this.updateNavigationState(windowId, view);
+        });
         view.webContents.on('did-change-theme-color', (e, color) => {
             if (view.isDestroyed()) return;
 
-            window.setTitle(view.webContents.getTitle());
-            db.bookmark.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
-                window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), color: color, isBookmarked: (docs.length > 0 ? true : false) });
-            });
+            if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
+                db.historys.update({ id: viewId }, { id: viewId, title: view.webContents.getTitle(), url: view.webContents.getURL() }, { upsert: true });
+
+            window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
+
+            this.updateViewState(windowId, view);
+            this.updateNavigationState(windowId, view);
+
+            window.webContents.send(`browserView-theme-color-${windowId}`, { id: view.webContents.id, color });
         });
 
+        view.webContents.on('update-target-url', (e, url) => {
+            /*
+            if (url.length > 0) {
+                view.webContents.executeJavaScript(
+                    `(function () {
+                        let dom = document.getElementById('tip-${executeJs}');
+                        
+                        document.getElementById('tip-${executeJs}').style.display = 'block';
+                        dom.textContent = '${url}';
+                    })()`
+                );
+            } else {
+                view.webContents.executeJavaScript(
+                    `(function () {
+                        document.getElementById('tip-${executeJs}').style.display = 'none';
+                    })()`
+                );
+            }
+            */
+        })
+
         view.webContents.on('new-window', (e, url) => {
             if (view.isDestroyed()) return;
 
@@ -586,241 +1098,515 @@ module.exports = class WindowManager {
             this.addView(windowId, url, true);
         });
 
+        view.webContents.on('certificate-error', (e, url, error, certificate, callback) => {
+            e.preventDefault();
+            if (Notification.isSupported()) {
+                const notify = new Notification({
+                    icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
+                    title: `プライバシー エラー`,
+                    body: '詳細はここをクリックしてください。',
+                    silent: true
+                });
+
+                notify.show();
+
+                notify.on('click', (e) => {
+                    dialog.showMessageBox({
+                        type: 'warning',
+                        title: 'プライバシー エラー',
+                        message: 'この接続ではプライバシーが保護されません',
+                        detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
+                        noLink: true,
+                        buttons: ['続行', 'キャンセル'],
+                        defaultId: 1,
+                        cancelId: 1
+                    }, (res) => {
+                        callback(res === 0);
+                    });
+                });
+                notify.on('close', (e) => {
+                    callback(false);
+                });
+            } else {
+                dialog.showMessageBox({
+                    type: 'warning',
+                    title: 'プライバシー エラー',
+                    message: 'この接続ではプライバシーが保護されません',
+                    detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
+                    noLink: true,
+                    buttons: ['続行', 'キャンセル'],
+                    defaultId: 1,
+                    cancelId: 1
+                }, (res) => {
+                    callback(res === 0);
+                });
+            }
+        });
+
         view.webContents.on('context-menu', (e, params) => {
             if (view.isDestroyed()) return;
 
-            const menu = Menu.buildFromTemplate(
-                [
-                    ...(params.linkURL !== '' ?
-                        [
-                            {
-                                label: '新しいタブで開く',
-                                click: () => {
-                                    this.addView(windowId, params.linkURL, true);
-                                }
-                            },
-                            {
-                                label: '新しいウィンドウで開く',
-                                enabled: false,
-                                click: () => { view.webContents.openDevTools(); }
-                            },
-                            {
-                                label: 'プライベート ウィンドウで開く',
-                                enabled: false,
-                                click: () => { view.webContents.openDevTools(); }
-                            },
-                            { type: 'separator' },
-                            {
-                                label: 'リンクをコピー',
-                                accelerator: 'CmdOrCtrl+C',
-                                click: () => {
-                                    clipboard.clear();
-                                    clipboard.writeText(params.linkURL);
-                                }
-                            },
-                            { type: 'separator' }
-                        ] : []),
-                    ...(params.hasImageContents ?
-                        [
-                            {
-                                label: '新しいタブで画像を開く',
-                                click: () => {
-                                    this.addView(windowId, params.srcURL, true);
-                                }
-                            },
-                            {
-                                label: '画像をコピー',
-                                click: () => {
-                                    const img = nativeImage.createFromDataURL(params.srcURL);
-
-                                    clipboard.clear();
-                                    clipboard.writeImage(img);
-                                }
-                            },
-                            {
-                                label: '画像アドレスをコピー',
-                                click: () => {
-                                    clipboard.clear();
-                                    clipboard.writeText(params.srcURL);
-                                }
-                            },
-                            { type: 'separator' }
-                        ] : []),
-                    ...(params.isEditable ?
-                        [
-                            {
-                                label: '元に戻す',
-                                accelerator: 'CmdOrCtrl+Z',
-                                enabled: params.editFlags.canUndo,
-                                click: () => { view.webContents.undo(); }
-                            },
-                            {
-                                label: 'やり直す',
-                                accelerator: 'CmdOrCtrl+Y',
-                                enabled: params.editFlags.canRedo,
-                                click: () => { view.webContents.redo(); }
-                            },
-                            { type: 'separator' },
-                            {
-                                label: '切り取り',
-                                accelerator: 'CmdOrCtrl+X',
-                                enabled: params.editFlags.canCut,
-                                click: () => { view.webContents.cut(); }
-                            },
-                            {
-                                label: 'コピー',
-                                accelerator: 'CmdOrCtrl+C',
-                                enabled: params.editFlags.canCopy,
-                                click: () => { view.webContents.copy(); }
-                            },
-                            {
-                                label: '貼り付け',
-                                accelerator: 'CmdOrCtrl+V',
-                                enabled: params.editFlags.canPaste,
-                                click: () => { view.webContents.paste(); }
-                            },
-                            { type: 'separator' },
-                            {
-                                label: 'すべて選択',
-                                accelerator: 'CmdOrCtrl+A',
-                                enabled: params.editFlags.canSelectAll,
-                                click: () => { view.webContents.selectAll(); }
-                            },
-                            { type: 'separator' }
-                        ] : []),
-                    ...(params.selectionText !== '' && !params.isEditable ?
-                        [
-                            {
-                                label: 'コピー',
-                                accelerator: 'CmdOrCtrl+C',
-                                click: () => { view.webContents.copy(); }
-                            },
-                            {
-                                label: `Googleで「${params.selectionText}」を検索`,
-                                click: () => {
-                                    this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
+            let menu;
+            if (params.linkURL !== '' && !params.hasImageContents) {
+                menu = Menu.buildFromTemplate(
+                    [
+                        {
+                            label: lang.window.view.contextMenu.link.newTab,
+                            click: () => {
+                                this.addView(windowId, params.linkURL, true);
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.link.newWindow,
+                            enabled: false,
+                            click: () => { view.webContents.openDevTools(); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.link.openPrivateWindow,
+                            enabled: false,
+                            click: () => { view.webContents.openDevTools(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.link.copy,
+                            accelerator: 'CmdOrCtrl+C',
+                            click: () => {
+                                clipboard.clear();
+                                clipboard.writeText(params.linkURL);
+                            }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.devTool,
+                            accelerator: 'CmdOrCtrl+Shift+I',
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                if (view.webContents.isDevToolsOpened())
+                                    view.webContents.devToolsWebContents.focus();
+                                else
+                                    view.webContents.openDevTools();
+                            }
+                        }
+                    ]
+                );
+            } else if (params.linkURL === '' && params.hasImageContents) {
+                menu = Menu.buildFromTemplate(
+                    [
+                        {
+                            label: lang.window.view.contextMenu.image.newTab,
+                            click: () => {
+                                this.addView(windowId, params.srcURL, true);
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.image.saveImage,
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                download(window, params.srcURL, {
+                                    directory: app.getPath('downloads'),
+                                    saveAs: true
+                                });
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.image.copyImage,
+                            click: () => {
+                                const img = nativeImage.createFromDataURL(params.srcURL);
+
+                                clipboard.clear();
+                                clipboard.writeImage(img);
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.image.copyLink,
+                            click: () => {
+                                clipboard.clear();
+                                clipboard.writeText(params.srcURL);
+                            }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.devTool,
+                            accelerator: 'CmdOrCtrl+Shift+I',
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                if (view.webContents.isDevToolsOpened())
+                                    view.webContents.devToolsWebContents.focus();
+                                else
+                                    view.webContents.openDevTools();
+                            }
+                        }
+                    ]
+                );
+            } else if (params.linkURL !== '' && params.hasImageContents) {
+                menu = Menu.buildFromTemplate(
+                    [
+                        {
+                            label: lang.window.view.contextMenu.link.newTab,
+                            click: () => {
+                                this.addView(windowId, params.linkURL, true);
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.link.newWindow,
+                            enabled: false,
+                            click: () => { view.webContents.openDevTools(); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.link.openPrivateWindow,
+                            enabled: false,
+                            click: () => { view.webContents.openDevTools(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.link.copy,
+                            accelerator: 'CmdOrCtrl+C',
+                            click: () => {
+                                clipboard.clear();
+                                clipboard.writeText(params.linkURL);
+                            }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.image.newTab,
+                            click: () => {
+                                this.addView(windowId, params.srcURL, true);
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.image.saveImage,
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                download(window, params.srcURL, {
+                                    directory: app.getPath('downloads'),
+                                    saveAs: true
+                                });
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.image.copyImage,
+                            click: () => {
+                                const img = nativeImage.createFromDataURL(params.srcURL);
+
+                                clipboard.clear();
+                                clipboard.writeImage(img);
+                            }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.image.copyLink,
+                            click: () => {
+                                clipboard.clear();
+                                clipboard.writeText(params.srcURL);
+                            }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.devTool,
+                            accelerator: 'CmdOrCtrl+Shift+I',
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                if (view.webContents.isDevToolsOpened())
+                                    view.webContents.devToolsWebContents.focus();
+                                else
+                                    view.webContents.openDevTools();
+                            }
+                        }
+                    ]
+                );
+            } else if (params.isEditable) {
+                menu = Menu.buildFromTemplate(
+                    [
+                        {
+                            label: lang.window.view.contextMenu.editable.undo,
+                            accelerator: 'CmdOrCtrl+Z',
+                            enabled: params.editFlags.canUndo,
+                            click: () => { view.webContents.undo(); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.editable.redo,
+                            accelerator: 'CmdOrCtrl+Y',
+                            enabled: params.editFlags.canRedo,
+                            click: () => { view.webContents.redo(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.editable.cut,
+                            accelerator: 'CmdOrCtrl+X',
+                            enabled: params.editFlags.canCut,
+                            click: () => { view.webContents.cut(); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.editable.copy,
+                            accelerator: 'CmdOrCtrl+C',
+                            enabled: params.editFlags.canCopy,
+                            click: () => { view.webContents.copy(); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.editable.paste,
+                            accelerator: 'CmdOrCtrl+V',
+                            enabled: params.editFlags.canPaste,
+                            click: () => { view.webContents.paste(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.editable.selectAll,
+                            accelerator: 'CmdOrCtrl+A',
+                            enabled: params.editFlags.canSelectAll,
+                            click: () => { view.webContents.selectAll(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.devTool,
+                            accelerator: 'CmdOrCtrl+Shift+I',
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                if (view.webContents.isDevToolsOpened())
+                                    view.webContents.devToolsWebContents.focus();
+                                else
+                                    view.webContents.openDevTools();
+                            }
+                        }
+                    ]
+                );
+            } else if (params.selectionText !== '' && !params.isEditable) {
+                menu = Menu.buildFromTemplate(
+                    [
+                        {
+                            label: lang.window.view.contextMenu.selection.copy,
+                            accelerator: 'CmdOrCtrl+C',
+                            click: () => { view.webContents.copy(); }
+                        },
+                        {
+                            label: String(lang.window.view.contextMenu.selection.textSearch).replace(/{name}/, 'Google').replace(/{text}/, params.selectionText),
+                            click: () => {
+                                this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
+                            }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.devTool,
+                            accelerator: 'CmdOrCtrl+Shift+I',
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                if (view.webContents.isDevToolsOpened())
+                                    view.webContents.devToolsWebContents.focus();
+                                else
+                                    view.webContents.openDevTools();
+                            }
+                        }
+                    ]
+                );
+            } else {
+                menu = Menu.buildFromTemplate(
+                    [
+                        {
+                            label: lang.window.view.contextMenu.back,
+                            accelerator: 'Alt+Left',
+                            icon: view.webContents.canGoBack() ? `${app.getAppPath()}/static/arrow_back.png` : `${app.getAppPath()}/static/arrow_back_inactive.png`,
+                            enabled: view.webContents.canGoBack(),
+                            click: () => { view.webContents.goBack(); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.forward,
+                            accelerator: 'Alt+Right',
+                            icon: view.webContents.canGoForward() ? `${app.getAppPath()}/static/arrow_forward.png` : `${app.getAppPath()}/static/arrow_forward_inactive.png`,
+                            enabled: view.webContents.canGoForward(),
+                            click: () => { view.webContents.goForward(); }
+                        },
+                        {
+                            label: !view.webContents.isLoadingMainFrame() ? lang.window.view.contextMenu.reload.reload : lang.window.view.contextMenu.reload.stop,
+                            accelerator: 'CmdOrCtrl+R',
+                            icon: !view.webContents.isLoadingMainFrame() ? `${app.getAppPath()}/static/refresh.png` : `${app.getAppPath()}/static/close.png`,
+                            click: () => { !view.webContents.isLoadingMainFrame() ? view.webContents.reload() : view.webContents.stop(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.floatingWindow,
+                            type: 'checkbox',
+                            checked: (floatingWindows.indexOf(windowId) != -1),
+                            enabled: (!window.isFullScreen() && !window.isMaximized() && config.get('design.isCustomTitlebar')),
+                            click: () => {
+                                if (floatingWindows.indexOf(windowId) != -1) {
+                                    floatingWindows.filter((win, i) => {
+                                        if (windowId == win) {
+                                            floatingWindows.splice(i, 1);
+                                        }
+                                    });
+                                } else {
+                                    floatingWindows.push(windowId);
                                 }
-                            },
-                            { type: 'separator' }
-                        ] : []),
-                    {
-                        label: '戻る',
-                        accelerator: 'Alt+Left',
-                        enabled: view.webContents.canGoBack(),
-                        click: () => { view.webContents.goBack(); }
-                    },
-                    {
-                        label: '進む',
-                        accelerator: 'Alt+Right',
-                        enabled: view.webContents.canGoForward(),
-                        click: () => { view.webContents.goForward(); }
-                    },
-                    {
-                        label: '再読み込み',
-                        accelerator: 'CmdOrCtrl+R',
-                        click: () => { view.webContents.reload(); }
-                    },
-                    { type: 'separator' },
-                    {
-                        label: 'Floating Window (Beta)',
-                        type: 'checkbox',
-                        checked: (floatingWindows.indexOf(windowId) != -1),
-                        enabled: (!window.isFullScreen() && !window.isMaximized()),
-                        click: () => {
-                            if (floatingWindows.indexOf(windowId) != -1) {
-                                floatingWindows.filter((win, i) => {
-                                    if (windowId == win) {
-                                        floatingWindows.splice(i, 1);
+                                this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
+                            }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.savePage,
+                            accelerator: 'CmdOrCtrl+S',
+                            icon: `${app.getAppPath()}/static/save.png`,
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                dialog.showSaveDialog(window, {
+                                    defaultPath: `${app.getPath('downloads')}/${view.webContents.getTitle()}.html`,
+                                    filters: [
+                                        { name: 'HTML', extensions: ['htm', 'html'] },
+                                        { name: 'All Files', extensions: ['*'] }
+                                    ]
+                                }, (fileName) => {
+                                    if (fileName != undefined) {
+                                        view.webContents.savePage(fileName, 'HTMLComplete', (err) => {
+                                            if (!err) console.log('Page Save successfully');
+                                        });
                                     }
                                 });
-                            } else {
-                                floatingWindows.push(windowId);
                             }
-                            this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
-                        }
-                    },
-                    { type: 'separator' },
-                    {
-                        label: 'ページの保存',
-                        accelerator: 'CmdOrCtrl+S',
-                        enabled: !view.webContents.getURL().startsWith('my://'),
-                        click: () => {
-                            view.webContents.savePage(`${app.getPath('downloads')}/${view.webContents.getTitle()}.html`, 'HTMLComplete', (err) => {
-                                if (!err) console.log('Page Save successfully');
-                            });
+                        },
+                        {
+                            label: lang.window.view.contextMenu.print,
+                            accelerator: 'CmdOrCtrl+P',
+                            icon: `${app.getAppPath()}/static/print.png`,
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => { view.webContents.print(); }
+                        },
+                        { type: 'separator' },
+                        {
+                            label: lang.window.view.contextMenu.viewSource,
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => { this.addView(windowId, `view-source:${view.webContents.getURL()}`, true); }
+                        },
+                        {
+                            label: lang.window.view.contextMenu.devTool,
+                            accelerator: 'CmdOrCtrl+Shift+I',
+                            enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
+                            click: () => {
+                                if (view.webContents.isDevToolsOpened())
+                                    view.webContents.devToolsWebContents.focus();
+                                else
+                                    view.webContents.openDevTools();
+                            }
                         }
-                    },
-                    {
-                        label: '印刷',
-                        accelerator: 'CmdOrCtrl+P',
-                        enabled: !view.webContents.getURL().startsWith('my://'),
-                        click: () => { view.webContents.print(); }
-                    },
-                    { type: 'separator' },
-                    {
-                        label: 'デベロッパーツール',
-                        accelerator: 'CmdOrCtrl+Shift+I',
-                        enabled: !view.webContents.getURL().startsWith('my://'),
-                        click: () => { if (view.webContents.isDevToolsOpened()) { view.webContents.devToolsWebContents.focus(); } else { view.webContents.openDevTools(); } }
-                    }
-                ]
-            );
+                    ]
+                );
+            }
 
             menu.popup();
         });
 
         view.webContents.on('before-input-event', (e, input) => {
             if (view.isDestroyed()) return;
+            window.webContents.setIgnoreMenuShortcuts(true);
 
-            if ((input.control || input.meta) && input.shift && input.key == 'I') {
-                e.preventDefault();
-                if (view.webContents.isDevToolsOpened()) {
-                    view.webContents.devToolsWebContents.focus();
-                } else {
-                    view.webContents.openDevTools();
+            if (input.control) {
+                if (input.shift && input.key == 'I') {
+                    e.preventDefault();
+                    window.webContents.setIgnoreMenuShortcuts(true);
+                    view.webContents.setIgnoreMenuShortcuts(true);
+
+                    if (view.webContents.isDevToolsOpened()) {
+                        view.webContents.devToolsWebContents.focus();
+                    } else {
+                        view.webContents.openDevTools();
+                    }
+                } else if (input.shift && input.key == 'R') {
+                    e.preventDefault();
+                    window.webContents.setIgnoreMenuShortcuts(true);
+                    view.webContents.setIgnoreMenuShortcuts(true);
+
+                    view.webContents.reloadIgnoringCache();
+                } else if (input.key == 'T') {
+                    e.preventDefault();
+                    window.webContents.setIgnoreMenuShortcuts(true);
+                    // view.webContents.setIgnoreMenuShortcuts(true);
+
+                    this.addView(windowId, config.get('homePage.defaultPage'), true);
+                }
+            } else if (input.alt) {
+                if (input.key == 'ArrowLeft') {
+                    e.preventDefault();
+                    // window.webContents.setIgnoreMenuShortcuts(true);
+                    view.webContents.setIgnoreMenuShortcuts(true);
+
+                    if (view.webContents.canGoBack())
+                        view.webContents.goBack();
+                } else if (input.key == 'ArrowRight') {
+                    e.preventDefault();
+                    // window.webContents.setIgnoreMenuShortcuts(true);
+                    view.webContents.setIgnoreMenuShortcuts(true);
+
+                    if (view.webContents.canGoForward())
+                        view.webContents.goForward();
+                }
+            } else {
+                if (input.key == 'F11') {
+                    e.preventDefault();
+                    window.webContents.setIgnoreMenuShortcuts(true);
+                    view.webContents.setIgnoreMenuShortcuts(true);
+
+                    window.setFullScreen(!window.isFullScreen());
+                    this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
                 }
-            } else if ((input.control || input.meta) && input.key == 'R') {
-                e.preventDefault();
-                view.webContents.reload();
-            } else if ((input.control || input.meta) && input.shift && input.key == 'R') {
-                e.preventDefault();
-                view.webContents.reloadIgnoringCache();
             }
         });
 
         view.webContents.session.on('will-download', (event, item, webContents) => {
+            if (id !== webContents.id) return;
+
+            const str = this.getRandString(12);
+            db.downloads.update({ id: str }, { id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() }, { upsert: true });
+
             item.on('updated', (e, state) => {
-                if (state === 'interrupted') {
-                    console.log('Download is interrupted but can be resumed')
-                } else if (state === 'progressing') {
-                    if (item.isPaused()) {
-                        console.log('Download is paused')
-                    } else {
-                        console.log(`Received bytes: ${item.getReceivedBytes()}`)
-                    }
-                }
+                db.downloads.update({ id: str }, { id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() }, { upsert: true });
             });
 
             item.once('done', (e, state) => {
+                const filePath = item.getSavePath();
+                db.downloads.update({ id: str }, { id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() }, { upsert: true });
                 if (state === 'completed') {
-                    console.log('Download successfully')
+                    window.webContents.send(`notification-${windowId}`, { id: id, content: `${item.getFilename()} のダウンロードが完了しました。` });
+
+                    if (!Notification.isSupported()) return;
+                    const notify = new Notification({
+                        icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
+                        title: 'ダウンロード完了',
+                        body: `${item.getFilename()} のダウンロードが完了しました。\n詳細はここをクリックしてください。`
+                    });
+
+                    notify.show();
+
+
+                    notify.on('click', (e) => {
+                        if (filePath !== undefined)
+                            shell.openItem(filePath);
+                    });
                 } else {
-                    console.log(`Download failed: ${state}`)
+                    console.log(`Download failed: ${state}`);
                 }
             });
         });
 
         view.webContents.loadURL(url);
 
-        views.push({ windowId, id, view });
-        console.log(views);
+        if (views[windowId] == undefined)
+            views[windowId] = [];
+        views[windowId].push({ id, view, isNotificationBar: false });
 
         if (isActive) {
-            window.webContents.send(`browserview-set-${windowId}`, { id: id });
+            window.webContents.send(`browserView-set-${windowId}`, { id: id });
             window.setBrowserView(view);
         }
 
         this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
         this.getViews(windowId);
     }
+
+    getRandString = (length) => {
+        const char = 'abcdefghijklmnopqrstuvwxyz0123456789';
+        const charLength = char.length;
+
+        let str = '';
+        for (var i = 0; i < length; i++) {
+            str += char[Math.floor(Math.random() * charLength)];
+        }
+
+        return str;
+    }
 }
\ No newline at end of file