OSDN Git Service

d78f82aea32059067b44a6afd75b441b5ab15beb
[serene/MyBrowser.git] / app / electron / WindowManager.js
1 const { app, shell, ipcMain, protocol, session, BrowserWindow, BrowserView, Menu, nativeImage, clipboard, dialog, Notification } = require('electron');
2 const path = require('path');
3 const { parse, format } = require('url');
4 const os = require('os');
5
6 const pkg = require(`${app.getAppPath()}/package.json`);
7 const protocolStr = 'flast';
8 const fileProtocolStr = `${protocolStr}-file`;
9
10 const { download } = require('electron-dl');
11 const platform = require('electron-platform');
12 const localShortcut = require('electron-localshortcut');
13
14 const Config = require('electron-store');
15 const config = new Config({
16     defaults: {
17         design: {
18             isHomeButton: false,
19             isBookmarkBar: false,
20             isDarkTheme: false,
21             isCustomTitlebar: true,
22             theme: 'default'
23         },
24         homePage: {
25             defaultPage: `${protocolStr}://home`,
26             defaultEngine: 'Google',
27             searchEngines: [
28                 {
29                     name: 'Google',
30                     url: 'https://www.google.com/search?q=%s'
31                 },
32                 {
33                     name: 'Bing',
34                     url: 'https://www.bing.com/search?q=%s'
35                 },
36                 {
37                     name: 'Yahoo! Japan',
38                     url: 'https://search.yahoo.co.jp/search?p=%s'
39                 },
40                 {
41                     name: 'goo',
42                     url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
43                 },
44                 {
45                     name: 'Baidu',
46                     url: 'https://www.baidu.com/s?wd=%s'
47                 },
48                 {
49                     name: 'Google Translate',
50                     url: 'https://translate.google.com/?text=%s'
51                 },
52                 {
53                     name: 'Youtube',
54                     url: 'https://www.youtube.com/results?search_query=%s'
55                 },
56                 {
57                     name: 'Twitter',
58                     url: 'https://www.twitter.com/search?q=%s'
59                 },
60                 {
61                     name: 'GitHub',
62                     url: 'https://github.com/search?q=%s'
63                 }
64             ]
65         },
66         isAdBlock: true,
67         language: 'ja',
68         window: {
69             isCloseConfirm: true,
70             isMaximized: false,
71             bounds: {
72                 width: 1100,
73                 height: 680
74             }
75         }
76     },
77 });
78
79 const lang = require(`${app.getAppPath()}/langs/${config.get('language')}.js`);
80
81 const Datastore = require('nedb');
82 let db = {};
83 db.pageSettings = new Datastore({
84     filename: path.join(app.getPath('userData'), 'Files', 'PageSettings.db'),
85     autoload: true,
86     timestampData: true
87 });
88
89 db.historys = new Datastore({
90     filename: path.join(app.getPath('userData'), 'Files', 'History.db'),
91     autoload: true,
92     timestampData: true
93 });
94 db.downloads = new Datastore({
95     filename: path.join(app.getPath('userData'), 'Files', 'Download.db'),
96     autoload: true,
97     timestampData: true
98 });
99 db.bookmarks = new Datastore({
100     filename: path.join(app.getPath('userData'), 'Files', 'Bookmarks.db'),
101     autoload: true,
102     timestampData: true
103 });
104
105 db.apps = new Datastore({
106     filename: path.join(app.getPath('userData'), 'Files', 'Apps.db'),
107     autoload: true,
108     timestampData: true
109 });
110
111 const { loadFilters, updateFilters, runAdblockService, removeAds } = require('./AdBlocker');
112
113 let floatingWindows = [];
114 let views = [];
115
116 getBaseWindow = (width = 1100, height = 680, minWidth = 500, minHeight = 360, x, y, frame = false) => {
117     return new BrowserWindow({
118         width, height, minWidth, minHeight, x, y, titleBarStyle: 'hidden', frame, fullscreenable: true,
119         icon: `${__dirname}/static/app/icon.png`,
120         show: false,
121         webPreferences: {
122             nodeIntegration: true,
123             webviewTag: true,
124             plugins: true,
125             experimentalFeatures: true,
126             contextIsolation: false,
127         }
128     });
129 }
130
131 loadSessionAndProtocol = () => {
132     const ses = session.defaultSession;
133
134     setPermissionRequestHandler(ses, false);
135
136     protocol.isProtocolHandled(protocolStr, (handled) => {
137         if (!handled) {
138             protocol.registerFileProtocol(protocolStr, (request, callback) => {
139                 const parsed = parse(request.url);
140
141                 return callback({
142                     path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
143                 });
144             }, (error) => {
145                 if (error) console.error('Failed to register protocol: ' + error);
146             });
147         }
148     });
149
150     protocol.isProtocolHandled(fileProtocolStr, (handled) => {
151         if (!handled) {
152             protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
153                 const parsed = parse(request.url);
154
155                 return callback({
156                     path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
157                 });
158             }, (error) => {
159                 if (error) console.error('Failed to register protocol: ' + error);
160             });
161         }
162     });
163 }
164
165 loadSessionAndProtocolWithPrivateMode = (windowId) => {
166     const ses = session.fromPartition(windowId);
167     ses.setUserAgent(ses.getUserAgent().replace(/ Electron\/[0-9\.]*/g, '') + ' PrivMode');
168
169     setPermissionRequestHandler(ses, true);
170
171     ses.protocol.registerFileProtocol(protocolStr, (request, callback) => {
172         const parsed = parse(request.url);
173
174         return callback({
175             path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
176         });
177     }, (error) => {
178         if (error) console.error('Failed to register protocol: ' + error);
179     });
180
181     ses.protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
182         const parsed = parse(request.url);
183
184         return callback({
185             path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
186         });
187     }, (error) => {
188         if (error) console.error('Failed to register protocol: ' + error);
189     });
190 }
191
192 setPermissionRequestHandler = (ses, isPrivate = false) => {
193     if (!isPrivate) {
194         ses.setPermissionRequestHandler((webContents, permission, callback) => {
195             const url = parse(webContents.getURL());
196
197             db.pageSettings.findOne({ origin: `${url.protocol}//${url.hostname}` }, (err, doc) => {
198                 if (doc != undefined) {
199                     if (permission == 'media' && doc.media != undefined && doc.media > -1)
200                         return callback(doc.media === 0);
201                     if (permission == 'geolocation' && doc.geolocation != undefined && doc.geolocation > -1)
202                         return callback(doc.geolocation === 0);
203                     if (permission == 'notifications' && doc.notifications != undefined && doc.notifications > -1)
204                         return callback(doc.notifications === 0);
205                     if (permission == 'midiSysex' && doc.midiSysex != undefined && doc.midiSysex > -1)
206                         return callback(doc.midiSysex === 0);
207                     if (permission == 'pointerLock' && doc.pointerLock != undefined && doc.pointerLock > -1)
208                         return callback(doc.pointerLock === 0);
209                     if (permission == 'fullscreen' && doc.fullscreen != undefined && doc.fullscreen > -1)
210                         return callback(doc.fullscreen === 0);
211                     if (permission == 'openExternal' && doc.openExternal != undefined && doc.openExternal > -1)
212                         return callback(doc.openExternal === 0);
213                 } else {
214                     if (Notification.isSupported()) {
215                         const notify = new Notification({
216                             icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
217                             title: `${url.protocol}//${url.hostname} が権限を要求しています。`,
218                             body: '詳細はここをクリックしてください。',
219                             silent: true
220                         });
221
222                         notify.show();
223
224                         notify.on('click', (e) => {
225                             dialog.showMessageBox({
226                                 type: 'info',
227                                 title: '権限の要求',
228                                 message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
229                                 detail: `要求内容: ${permission}`,
230                                 checkboxLabel: 'このサイトでは今後も同じ処理をする',
231                                 checkboxChecked: false,
232                                 noLink: true,
233                                 buttons: ['はい / Yes', 'いいえ / No'],
234                                 defaultId: 0,
235                                 cancelId: 1
236                             }, (res, checked) => {
237                                 console.log(res, checked);
238                                 if (checked) {
239                                     if (permission == 'media')
240                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, media: res }, { upsert: true });
241                                     if (permission == 'geolocation')
242                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, geolocation: res }, { upsert: true });
243                                     if (permission == 'notifications')
244                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, notifications: res }, { upsert: true });
245                                     if (permission == 'midiSysex')
246                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, midiSysex: res }, { upsert: true });
247                                     if (permission == 'pointerLock')
248                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, pointerLock: res }, { upsert: true });
249                                     if (permission == 'fullscreen')
250                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, fullscreen: res }, { upsert: true });
251                                     if (permission == 'openExternal')
252                                         db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, openExternal: res }, { upsert: true });
253                                 }
254                                 return callback(res === 0);
255                             });
256                         });
257                         notify.on('close', (e) => {
258                             return callback(false);
259                         });
260                     } else {
261                         dialog.showMessageBox({
262                             type: 'info',
263                             title: '権限の要求',
264                             message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
265                             detail: `要求内容: ${permission}`,
266                             checkboxLabel: 'このサイトでは今後も同じ処理をする',
267                             checkboxChecked: false,
268                             noLink: true,
269                             buttons: ['はい / Yes', 'いいえ / No'],
270                             defaultId: 0,
271                             cancelId: 1
272                         }, (res, checked) => {
273                             if (checked) {
274                                 if (permission == 'media')
275                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, media: res }, { upsert: true });
276                                 if (permission == 'geolocation')
277                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, geolocation: res }, { upsert: true });
278                                 if (permission == 'notifications')
279                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, notifications: res }, { upsert: true });
280                                 if (permission == 'midiSysex')
281                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, midiSysex: res }, { upsert: true });
282                                 if (permission == 'pointerLock')
283                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, pointerLock: res }, { upsert: true });
284                                 if (permission == 'fullscreen')
285                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, fullscreen: res }, { upsert: true });
286                                 if (permission == 'openExternal')
287                                     db.pageSettings.update({ origin: `${url.protocol}//${url.hostname}` }, { origin: `${url.protocol}//${url.hostname}`, openExternal: res }, { upsert: true });
288                             }
289                             return callback(res === 0);
290                         });
291                     }
292                 }
293             });
294         });
295     } else {
296         ses.setPermissionRequestHandler((webContents, permission, callback) => {
297             const url = parse(webContents.getURL());
298             if (Notification.isSupported()) {
299                 const notify = new Notification({
300                     icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
301                     title: `${url.protocol}//${url.hostname} が権限を要求しています。`,
302                     body: '詳細はここをクリックしてください。\nプライベート ウィンドウ',
303                     silent: true
304                 });
305
306                 notify.show();
307
308                 notify.on('click', (e) => {
309                     dialog.showMessageBox({
310                         type: 'info',
311                         title: '権限の要求',
312                         message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
313                         detail: `要求内容: ${permission}`,
314                         noLink: true,
315                         buttons: ['はい / Yes', 'いいえ / No'],
316                         defaultId: 0,
317                         cancelId: 1
318                     }, (res) => {
319                         return callback(res === 0);
320                     });
321                 });
322                 notify.on('close', (e) => {
323                     return callback(false);
324                 });
325             } else {
326                 dialog.showMessageBox({
327                     type: 'info',
328                     title: '権限の要求',
329                     message: `${url.protocol}//${url.hostname} が権限を要求しています。`,
330                     detail: `要求内容: ${permission}`,
331                     noLink: true,
332                     buttons: ['はい / Yes', 'いいえ / No'],
333                     defaultId: 0,
334                     cancelId: 1
335                 }, (res) => {
336                     return callback(res === 0);
337                 });
338             }
339         });
340     }
341 }
342
343 module.exports = class WindowManager {
344     constructor() {
345         this.windows = new Map();
346
347         ipcMain.on('window-add', (e, args) => {
348             this.addWindow(args.isPrivate);
349         });
350
351         ipcMain.on('window-fixBounds', (e, args) => {
352             this.windows.forEach((value, key) => {
353                 this.fixBounds(key, (floatingWindows.indexOf(key) != -1));
354             })
355         });
356
357         ipcMain.on('window-change-settings', (e, args) => {
358             this.windows.forEach((value, key) => {
359                 value.webContents.send('window-change-settings', {});
360             })
361         });
362
363         ipcMain.on('update-filters', (e, args) => {
364             updateFilters();
365         });
366
367         ipcMain.on('data-history-get', (e, args) => {
368             db.historys.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
369                 e.sender.send('data-history-get', { historys: docs });
370             });
371         });
372
373         ipcMain.on('data-history-clear', (e, args) => {
374             db.historys.remove({}, { multi: true });
375         });
376
377         ipcMain.on('data-downloads-get', (e, args) => {
378             db.downloads.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
379                 e.sender.send('data-downloads-get', { downloads: docs });
380             });
381         });
382
383         ipcMain.on('data-downloads-clear', (e, args) => {
384             db.downloads.remove({}, { multi: true });
385         });
386
387         ipcMain.on('data-bookmarks-get', (e, args) => {
388             db.bookmarks.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
389                 e.sender.send('data-bookmarks-get', { bookmarks: docs });
390             });
391         });
392
393         ipcMain.on('data-bookmarks-clear', (e, args) => {
394             db.bookmarks.remove({}, { multi: true });
395         });
396
397         ipcMain.on('data-apps-add', (e, args) => {
398             db.apps.update({ id: args.id }, { id: args.id, name: args.name, description: args.description, url: args.url }, { upsert: true });
399
400             db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
401                 console.log(docs)
402             });
403         });
404
405         ipcMain.on('data-apps-remove', (e, args) => {
406             db.apps.remove({ id: args.id }, {});
407         });
408
409         ipcMain.on('data-apps-get', (e, args) => {
410             db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
411                 e.sender.send('data-apps-get', { apps: docs });
412             });
413         });
414
415         ipcMain.on('data-apps-is', (e, args) => {
416             db.apps.find({ id: args.id }).exec((err, docs) => {
417                 e.sender.send('data-apps-is', { id: args.id, isInstalled: (docs.length > 0 ? true : false) });
418             });
419         });
420
421         ipcMain.on('data-apps-clear', (e, args) => {
422             db.apps.remove({}, { multi: true });
423         });
424
425         ipcMain.on('clear-browsing-data', () => {
426             const ses = session.defaultSession;
427             ses.clearCache((err) => {
428                 if (err) log.error(err);
429             });
430
431             ses.clearStorageData({
432                 storages: [
433                     'appcache',
434                     'cookies',
435                     'filesystem',
436                     'indexdb',
437                     'localstorage',
438                     'shadercache',
439                     'websql',
440                     'serviceworkers',
441                     'cachestorage',
442                 ],
443             });
444
445             config.clear();
446             db.pageSettings.remove({}, { multi: true });
447
448             db.historys.remove({}, { multi: true });
449             db.downloads.remove({}, { multi: true });
450             db.bookmarks.remove({}, { multi: true });
451             db.apps.remove({}, { multi: true });
452         });
453     }
454
455     addWindow = (isPrivate = false) => {
456         loadSessionAndProtocol();
457         loadFilters();
458
459         const { width, height, x, y } = config.get('window.bounds');
460         const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 500, 360, x, y, !config.get('design.isCustomTitlebar'));
461
462         const id = (!isPrivate ? `window-${window.id}` : `private-${window.id}`);
463
464         config.get('window.isMaximized') && window.maximize();
465
466         const startUrl = process.env.ELECTRON_START_URL || format({
467             pathname: path.join(__dirname, '/../build/index.html'), // 警告:このファイルを移動する場合ここの相対パスの指定に注意してください
468             protocol: 'file:',
469             slashes: true,
470             hash: `/window/${id}`,
471         });
472
473         window.loadURL(startUrl);
474
475         window.once('ready-to-show', () => {
476             window.show();
477         });
478
479         window.on('closed', () => {
480             this.windows.delete(id);
481         });
482
483         window.on('close', (e) => {
484             delete views[id];
485
486             config.set('window.isMaximized', window.isMaximized());
487             config.set('window.bounds', window.getBounds());
488         });
489
490         window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
491         window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
492         window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
493         window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
494         window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
495         window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
496
497         // registerProtocols();
498         this.registerListeners(id);
499
500         localShortcut.register(window, 'CmdOrCtrl+Shift+I', () => {
501             if (window.getBrowserView() == undefined) return;
502             const view = window.getBrowserView();
503
504             if (view.webContents.isDevToolsOpened()) {
505                 view.webContents.devToolsWebContents.focus();
506             } else {
507                 view.webContents.openDevTools();
508             }
509         });
510
511         localShortcut.register(window, 'CmdOrCtrl+R', () => {
512             if (window.getBrowserView() == undefined) return;
513             const view = window.getBrowserView();
514
515             view.webContents.reload();
516         });
517
518         localShortcut.register(window, 'CmdOrCtrl+Shift+R', () => {
519             if (window.getBrowserView() == undefined) return;
520             const view = window.getBrowserView();
521
522             view.webContents.reloadIgnoringCache();
523         });
524
525         this.windows.set(id, window);
526
527         if (process.argv != undefined) {
528             window.webContents.send(`tab-add-${id}`, { url: process.argv[process.argv.length - 1] });
529         }
530     }
531
532     registerListeners = (id) => {
533         ipcMain.on(`browserview-add-${id}`, (e, args) => {
534             this.addView(id, args.url, args.isActive);
535         });
536
537         ipcMain.on(`browserview-remove-${id}`, (e, args) => {
538             this.removeView(id, args.id);
539         });
540
541         ipcMain.on(`browserview-select-${id}`, (e, args) => {
542             this.selectView(id, args.id);
543         });
544
545         ipcMain.on(`browserview-get-${id}`, (e, args) => {
546             let datas = [];
547
548             views[id].map((item) => {
549                 const url = item.view.webContents.getURL();
550
551                 datas.push({ id: item.view.webContents.id, title: item.view.webContents.getTitle(), url, icon: this.getFavicon(url), color: '#0a84ff', isBookmarked: false });
552             });
553             e.sender.send(`browserview-get-${id}`, { views: datas });
554         });
555
556         ipcMain.on(`browserview-goBack-${id}`, (e, args) => {
557             views[id].filter(function (view, i) {
558                 if (view.view.webContents.id == args.id) {
559                     let webContents = views[id][i].view.webContents;
560                     if (webContents.canGoBack())
561                         webContents.goBack();
562                 }
563             });
564         });
565
566         ipcMain.on(`browserview-goForward-${id}`, (e, args) => {
567             views[id].filter(function (view, i) {
568                 if (view.view.webContents.id == args.id) {
569                     let webContents = views[id][i].view.webContents;
570                     if (webContents.canGoForward())
571                         webContents.goForward();
572                 }
573             });
574         });
575
576         ipcMain.on(`browserview-reload-${id}`, (e, args) => {
577             views[id].filter(function (view, i) {
578                 if (view.view.webContents.id == args.id) {
579                     let webContents = views[id][i].view.webContents;
580                     webContents.reload();
581                 }
582             });
583         });
584
585         ipcMain.on(`browserview-stop-${id}`, (e, args) => {
586             views[id].filter(function (view, i) {
587                 if (view.view.webContents.id == args.id) {
588                     let webContents = views[id][i].view.webContents;
589                     webContents.stop();
590                 }
591             });
592         });
593
594         ipcMain.on(`browserview-goHome-${id}`, (e, args) => {
595             views[id].filter(function (view, i) {
596                 if (view.view.webContents.id == args.id) {
597                     let webContents = views[id][i].view.webContents;
598                     webContents.loadURL(config.get('homePage.defaultPage'));
599                 }
600             });
601         });
602
603         ipcMain.on(`browserview-loadURL-${id}`, (e, args) => {
604             views[id].filter(function (view, i) {
605                 if (view.view.webContents.id == args.id) {
606                     let webContents = views[id][i].view.webContents;
607                     webContents.loadURL(args.url);
608                 }
609             });
610         });
611
612         ipcMain.on(`browserview-loadFile-${id}`, (e, args) => {
613             views[id].filter(function (view, i) {
614                 if (view.view.webContents.id == args.id) {
615                     let webContents = views[id][i].view.webContents;
616                     webContents.loadFile(args.url);
617                 }
618             });
619         });
620
621         ipcMain.on(`data-bookmark-add-${id}`, (e, args) => {
622             views[id].filter((view, i) => {
623                 if (view.view.webContents.id == args.id) {
624                     let v = views[id][i].view;
625                     db.bookmarks.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
626                     this.updateBookmarkState(id, v);
627                 }
628             });
629         });
630
631         ipcMain.on(`data-bookmark-remove-${id}`, (e, args) => {
632             views[id].filter((view, i) => {
633                 if (view.view.webContents.id == args.id) {
634                     let v = views[id][i].view;
635                     db.bookmarks.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
636                     this.updateBookmarkState(id, v);
637                 }
638             });
639         });
640
641         ipcMain.on(`data-bookmark-has-${id}`, (e, args) => {
642             views[id].filter((view, i) => {
643                 if (view.view.webContents.id == args.id) {
644                     let v = views[id][i].view;
645                     db.bookmarks.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
646                         e.sender.send(`data-bookmark-has-${id}`, { isBookmarked: (docs.length > 0 ? true : false) });
647                     });
648                 }
649             });
650         });
651     }
652
653     getFavicon = (url) => {
654         const parsed = parse(url);
655         return url.startsWith(`${protocolStr}://`) || url.startsWith(`${fileProtocolStr}://`) ? undefined : `https://www.google.com/s2/favicons?domain=${parsed.protocol}//${parsed.hostname}`;
656     }
657
658     updateNavigationState = (id, view) => {
659         const window = this.windows.get(id);
660         window.webContents.send(`update-navigation-state-${id}`, {
661             id: view.webContents.id,
662             canGoBack: view.webContents.canGoBack(),
663             canGoForward: view.webContents.canGoForward(),
664         });
665     }
666
667     updateBookmarkState = (id, view) => {
668         const window = this.windows.get(id);
669         db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(id).startsWith('private')) }, (err, docs) => {
670             const url = view.webContents.getURL();
671
672             window.webContents.send(`browserview-load-${id}`, { id: view.webContents.id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), color: '#0a84ff', isBookmarked: (docs.length > 0 ? true : false) });
673         });
674     }
675
676     getColor = (view) => {
677         return new Promise((resolve, reject) => {
678             if (view !== null && !view.isDestroyed() && view.webContents !== null) {
679                 view.webContents.executeJavaScript(
680                     `(function () {
681                         const heads = document.head.children;
682                         for (var i = 0; i < heads.length; i++) {
683                             if (heads[i].getAttribute('name') === 'theme-color') {
684                                 return heads[i].getAttribute('content');
685                             }
686                         } 
687                     })()`, false, async (result) => {
688                         resolve(result !== null ? result : '#0a84ff');
689                     });
690             } else {
691                 reject(new Error('WebContents are not available'))
692             }
693         });
694     };
695
696     fixBounds = (windowId, isFloating = false) => {
697         const window = this.windows.get(windowId);
698
699         if (window.getBrowserView() == undefined) return;
700         const view = window.getBrowserView();
701
702         const { width, height } = window.getContentBounds();
703
704         const baseBarHeight = 73;
705         const bookMarkBarHeight = 28;
706
707         view.setAutoResize({ width: true, height: true });
708         if (isFloating) {
709             window.setMinimizable(false);
710             window.setMaximizable(false);
711             window.setAlwaysOnTop(true);
712             window.setVisibleOnAllWorkspaces(true);
713             view.setBounds({
714                 x: 1,
715                 y: 1,
716                 width: width - 2,
717                 height: height - 2,
718             });
719         } else {
720             window.setMinimizable(true);
721             window.setMaximizable(true);
722             window.setAlwaysOnTop(false);
723             window.setVisibleOnAllWorkspaces(false);
724             if (window.isFullScreen()) {
725                 view.setBounds({
726                     x: 0,
727                     y: 0,
728                     width: width,
729                     height: height,
730                 });
731             } else {
732                 view.setBounds({
733                     x: config.get('design.isCustomTitlebar') ? 1 : 0,
734                     y: config.get('design.isCustomTitlebar') ? this.getHeight(true, height, baseBarHeight, bookMarkBarHeight) + 1 : this.getHeight(true, height, baseBarHeight, bookMarkBarHeight),
735                     width: config.get('design.isCustomTitlebar') ? width - 2 : width,
736                     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),
737                 });
738             }
739         }
740         view.setAutoResize({ width: true, height: true });
741     }
742
743     getHeight = (b, height, baseBarHeight, bookMarkBarHeight) => {
744         if (b) {
745             return config.get('design.isBookmarkBar') ? (baseBarHeight + bookMarkBarHeight) : baseBarHeight;
746         } else {
747             return height - (config.get('design.isBookmarkBar') ? (baseBarHeight + bookMarkBarHeight) : baseBarHeight);
748         }
749     };
750
751     addView = (id, url, isActive) => {
752         if (String(id).startsWith('private')) {
753             loadSessionAndProtocolWithPrivateMode(id);
754         }
755
756         this.addTab(id, url, isActive);
757     }
758
759     removeView = (windowId, id) => {
760         views[windowId].filter((view, i) => {
761             if (view.view.webContents.id == id) {
762                 const index = i;
763
764                 if (index + 1 < views[windowId].length) {
765                     this.selectView2(windowId, index + 1);
766                 } else if (index - 1 >= 0) {
767                     this.selectView2(windowId, index - 1);
768                 }
769
770                 views[windowId][index].view.destroy();
771                 views[windowId].splice(index, 1);
772             }
773         });
774     }
775
776     selectView = (windowId, id) => {
777         const window = this.windows.get(windowId);
778         views[windowId].filter((view, i) => {
779             if (id == view.view.webContents.id) {
780                 window.setBrowserView(views[windowId][i].view);
781                 window.setTitle(`${views[windowId][i].view.webContents.getTitle()} - ${pkg.name}`);
782                 window.webContents.send(`browserview-set-${windowId}`, { id: id });
783                 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
784             }
785         });
786     }
787
788     selectView2 = (windowId, i) => {
789         const window = this.windows.get(windowId);
790         const item = views[windowId][i];
791
792         window.setBrowserView(item.view);
793         window.setTitle(`${item.view.webContents.getTitle()} - ${pkg.name}`);
794         window.webContents.send(`browserview-set-${windowId}`, { id: item.id });
795         this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
796     }
797
798     getViews = (windowId) => {
799         let datas = [];
800         for (var i = 0; i < views[windowId].length; i++) {
801             const url = views[windowId][i].view.webContents.getURL();
802
803             datas.push({ id: views[windowId][i].view.webContents.id, title: views[windowId][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
804         }
805         const window = this.windows.get(windowId);
806         window.webContents.send(`browserview-get-${windowId}`, { views: datas });
807     }
808
809     addTab = (windowId, url = config.get('homePage.defaultPage'), isActive = true) => {
810         const view = new BrowserView({
811             webPreferences: {
812                 nodeIntegration: false,
813                 contextIsolation: false,
814                 plugins: true,
815                 experimentalFeatures: true,
816                 safeDialogs: true,
817                 safeDialogsMessage: '今後このページではダイアログを表示しない',
818                 ...(String(windowId).startsWith('private') && { partition: windowId }),
819                 preload: require.resolve('./Preload')
820             }
821         });
822
823         const window = this.windows.get(windowId);
824         const id = view.webContents.id;
825
826         runAdblockService(window, windowId, id, view.webContents.session);
827
828         view.webContents.on('did-start-loading', () => {
829             if (view.isDestroyed()) return;
830
831             window.webContents.send(`browserview-start-loading-${windowId}`, { id: id });
832         });
833         view.webContents.on('did-stop-loading', () => {
834             if (view.isDestroyed()) return;
835
836             window.webContents.send(`browserview-stop-loading-${windowId}`, { id: id });
837         });
838
839         view.webContents.on('did-finish-load', (e) => {
840             if (view.isDestroyed()) return;
841
842             window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
843             this.updateBookmarkState(windowId, view);
844
845             this.updateNavigationState(windowId, view);
846         });
847         view.webContents.on('did-fail-load', (e, code, description, url, isMainFrame, processId, routingId) => {
848             if (view.isDestroyed() || !isMainFrame || code === -3) return;
849
850             dialog.showMessageBox({ message: `${code}: ${description}` });
851         });
852
853         view.webContents.on('did-start-navigation', (e) => {
854             if (view.isDestroyed()) return;
855
856             const url = view.webContents.getURL();
857
858             if (config.get('isAdBlock') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
859                 removeAds(url, view.webContents);
860
861             this.updateNavigationState(windowId, view);
862         });
863
864         view.webContents.on('page-title-updated', (e) => {
865             if (view.isDestroyed()) return;
866
867             window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
868             this.updateBookmarkState(windowId, view);
869
870             if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
871                 db.historys.insert({ title: view.webContents.getTitle(), url: view.webContents.getURL() });
872
873             this.updateNavigationState(windowId, view);
874         });
875
876         view.webContents.on('page-favicon-updated', (e, favicons) => {
877             if (view.isDestroyed()) return;
878
879             window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
880
881             this.updateBookmarkState(windowId, view);
882             this.updateNavigationState(windowId, view);
883         });
884
885         view.webContents.on('did-change-theme-color', (e, color) => {
886             if (view.isDestroyed()) return;
887
888             window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
889
890             this.updateBookmarkState(windowId, view);
891             this.updateNavigationState(windowId, view);
892
893             window.webContents.send(`browserview-theme-color-${windowId}`, { id: view.webContents.id, color });
894         });
895
896         view.webContents.on('new-window', (e, url) => {
897             if (view.isDestroyed()) return;
898
899             e.preventDefault();
900             this.addView(windowId, url, true);
901         });
902
903         view.webContents.on('certificate-error', (e, url, error, certificate, callback) => {
904             e.preventDefault();
905             if (Notification.isSupported()) {
906                 const notify = new Notification({
907                     icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
908                     title: `プライバシー エラー`,
909                     body: '詳細はここをクリックしてください。',
910                     silent: true
911                 });
912
913                 notify.show();
914
915                 notify.on('click', (e) => {
916                     dialog.showMessageBox({
917                         type: 'warning',
918                         title: 'プライバシー エラー',
919                         message: 'この接続ではプライバシーが保護されません',
920                         detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
921                         noLink: true,
922                         buttons: ['続行', 'キャンセル'],
923                         defaultId: 1,
924                         cancelId: 1
925                     }, (res) => {
926                         callback(res === 0);
927                     });
928                 });
929                 notify.on('close', (e) => {
930                     callback(false);
931                 });
932             } else {
933                 dialog.showMessageBox({
934                     type: 'warning',
935                     title: 'プライバシー エラー',
936                     message: 'この接続ではプライバシーが保護されません',
937                     detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
938                     noLink: true,
939                     buttons: ['続行', 'キャンセル'],
940                     defaultId: 1,
941                     cancelId: 1
942                 }, (res) => {
943                     callback(res === 0);
944                 });
945             }
946         });
947
948         view.webContents.on('context-menu', (e, params) => {
949             if (view.isDestroyed()) return;
950
951             let menu;
952             if (params.linkURL !== '') {
953                 menu = Menu.buildFromTemplate(
954                     [
955                         {
956                             label: lang.window.view.contextMenu.link.newTab,
957                             click: () => {
958                                 this.addView(windowId, `view-source:${view.webContents.getURL()}`, false);
959                                 this.addView(windowId, params.linkURL, true);
960                             }
961                         },
962                         {
963                             label: lang.window.view.contextMenu.link.newWindow,
964                             enabled: false,
965                             click: () => { view.webContents.openDevTools(); }
966                         },
967                         {
968                             label: lang.window.view.contextMenu.link.openPrivateWindow,
969                             enabled: false,
970                             click: () => { view.webContents.openDevTools(); }
971                         },
972                         { type: 'separator' },
973                         {
974                             label: lang.window.view.contextMenu.link.copy,
975                             accelerator: 'CmdOrCtrl+C',
976                             click: () => {
977                                 clipboard.clear();
978                                 clipboard.writeText(params.linkURL);
979                             }
980                         },
981                         { type: 'separator' },
982                         {
983                             label: lang.window.view.contextMenu.devTool,
984                             accelerator: 'CmdOrCtrl+Shift+I',
985                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
986                             click: () => {
987                                 if (view.webContents.isDevToolsOpened())
988                                     view.webContents.devToolsWebContents.focus();
989                                 else
990                                     view.webContents.openDevTools();
991                             }
992                         }
993                     ]
994                 );
995             } else if (params.hasImageContents) {
996                 menu = Menu.buildFromTemplate(
997                     [
998                         {
999                             label: lang.window.view.contextMenu.image.newTab,
1000                             click: () => {
1001                                 this.addView(windowId, params.srcURL, true);
1002                             }
1003                         },
1004                         {
1005                             label: lang.window.view.contextMenu.image.saveImage,
1006                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1007                             click: () => {
1008                                 download(window, params.srcURL, {
1009                                     directory: app.getPath('downloads'),
1010                                     saveAs: true
1011                                 });
1012                             }
1013                         },
1014                         {
1015                             label: lang.window.view.contextMenu.image.copyImage,
1016                             click: () => {
1017                                 const img = nativeImage.createFromDataURL(params.srcURL);
1018
1019                                 clipboard.clear();
1020                                 clipboard.writeImage(img);
1021                             }
1022                         },
1023                         {
1024                             label: lang.window.view.contextMenu.image.copyLink,
1025                             click: () => {
1026                                 clipboard.clear();
1027                                 clipboard.writeText(params.srcURL);
1028                             }
1029                         },
1030                         { type: 'separator' },
1031                         {
1032                             label: lang.window.view.contextMenu.devTool,
1033                             accelerator: 'CmdOrCtrl+Shift+I',
1034                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1035                             click: () => {
1036                                 if (view.webContents.isDevToolsOpened())
1037                                     view.webContents.devToolsWebContents.focus();
1038                                 else
1039                                     view.webContents.openDevTools();
1040                             }
1041                         }
1042                     ]
1043                 );
1044             } else if (params.isEditable) {
1045                 menu = Menu.buildFromTemplate(
1046                     [
1047                         {
1048                             label: lang.window.view.contextMenu.editable.undo,
1049                             accelerator: 'CmdOrCtrl+Z',
1050                             enabled: params.editFlags.canUndo,
1051                             click: () => { view.webContents.undo(); }
1052                         },
1053                         {
1054                             label: lang.window.view.contextMenu.editable.redo,
1055                             accelerator: 'CmdOrCtrl+Y',
1056                             enabled: params.editFlags.canRedo,
1057                             click: () => { view.webContents.redo(); }
1058                         },
1059                         { type: 'separator' },
1060                         {
1061                             label: lang.window.view.contextMenu.editable.cut,
1062                             accelerator: 'CmdOrCtrl+X',
1063                             enabled: params.editFlags.canCut,
1064                             click: () => { view.webContents.cut(); }
1065                         },
1066                         {
1067                             label: lang.window.view.contextMenu.editable.copy,
1068                             accelerator: 'CmdOrCtrl+C',
1069                             enabled: params.editFlags.canCopy,
1070                             click: () => { view.webContents.copy(); }
1071                         },
1072                         {
1073                             label: lang.window.view.contextMenu.editable.paste,
1074                             accelerator: 'CmdOrCtrl+V',
1075                             enabled: params.editFlags.canPaste,
1076                             click: () => { view.webContents.paste(); }
1077                         },
1078                         { type: 'separator' },
1079                         {
1080                             label: lang.window.view.contextMenu.editable.selectAll,
1081                             accelerator: 'CmdOrCtrl+A',
1082                             enabled: params.editFlags.canSelectAll,
1083                             click: () => { view.webContents.selectAll(); }
1084                         },
1085                         { type: 'separator' },
1086                         {
1087                             label: lang.window.view.contextMenu.devTool,
1088                             accelerator: 'CmdOrCtrl+Shift+I',
1089                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1090                             click: () => {
1091                                 if (view.webContents.isDevToolsOpened())
1092                                     view.webContents.devToolsWebContents.focus();
1093                                 else
1094                                     view.webContents.openDevTools();
1095                             }
1096                         }
1097                     ]
1098                 );
1099             } else if (params.selectionText !== '' && !params.isEditable) {
1100                 menu = Menu.buildFromTemplate(
1101                     [
1102                         {
1103                             label: lang.window.view.contextMenu.selection.copy,
1104                             accelerator: 'CmdOrCtrl+C',
1105                             click: () => { view.webContents.copy(); }
1106                         },
1107                         {
1108                             label: String(lang.window.view.contextMenu.selection.textSearch).replace(/{name}/, 'Google').replace(/{text}/, params.selectionText),
1109                             click: () => {
1110                                 this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
1111                             }
1112                         },
1113                         { type: 'separator' },
1114                         {
1115                             label: lang.window.view.contextMenu.devTool,
1116                             accelerator: 'CmdOrCtrl+Shift+I',
1117                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1118                             click: () => {
1119                                 if (view.webContents.isDevToolsOpened())
1120                                     view.webContents.devToolsWebContents.focus();
1121                                 else
1122                                     view.webContents.openDevTools();
1123                             }
1124                         }
1125                     ]
1126                 );
1127             } else {
1128                 menu = Menu.buildFromTemplate(
1129                     [
1130                         {
1131                             label: lang.window.view.contextMenu.back,
1132                             accelerator: 'Alt+Left',
1133                             icon: view.webContents.canGoBack() ? `${app.getAppPath()}/static/arrow_back.png` : `${app.getAppPath()}/static/arrow_back_inactive.png`,
1134                             enabled: view.webContents.canGoBack(),
1135                             click: () => { view.webContents.goBack(); }
1136                         },
1137                         {
1138                             label: lang.window.view.contextMenu.forward,
1139                             accelerator: 'Alt+Right',
1140                             icon: view.webContents.canGoForward() ? `${app.getAppPath()}/static/arrow_forward.png` : `${app.getAppPath()}/static/arrow_forward_inactive.png`,
1141                             enabled: view.webContents.canGoForward(),
1142                             click: () => { view.webContents.goForward(); }
1143                         },
1144                         {
1145                             label: !view.webContents.isLoadingMainFrame() ? lang.window.view.contextMenu.reload.reload : lang.window.view.contextMenu.reload.stop,
1146                             accelerator: 'CmdOrCtrl+R',
1147                             icon: !view.webContents.isLoadingMainFrame() ? `${app.getAppPath()}/static/refresh.png` : `${app.getAppPath()}/static/close.png`,
1148                             click: () => { !view.webContents.isLoadingMainFrame() ? view.webContents.reload() : view.webContents.stop(); }
1149                         },
1150                         { type: 'separator' },
1151                         {
1152                             label: lang.window.view.contextMenu.floatingWindow,
1153                             type: 'checkbox',
1154                             checked: (floatingWindows.indexOf(windowId) != -1),
1155                             enabled: (!window.isFullScreen() && !window.isMaximized() && config.get('design.isCustomTitlebar')),
1156                             click: () => {
1157                                 if (floatingWindows.indexOf(windowId) != -1) {
1158                                     floatingWindows.filter((win, i) => {
1159                                         if (windowId == win) {
1160                                             floatingWindows.splice(i, 1);
1161                                         }
1162                                     });
1163                                 } else {
1164                                     floatingWindows.push(windowId);
1165                                 }
1166                                 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1167                             }
1168                         },
1169                         { type: 'separator' },
1170                         {
1171                             label: lang.window.view.contextMenu.savePage,
1172                             accelerator: 'CmdOrCtrl+S',
1173                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1174                             click: () => {
1175                                 dialog.showSaveDialog(window, {
1176                                     defaultPath: `${app.getPath('downloads')}/${view.webContents.getTitle()}.html`,
1177                                     filters: [
1178                                         { name: 'HTML', extensions: ['htm', 'html'] },
1179                                         { name: 'All Files', extensions: ['*'] }
1180                                     ]
1181                                 }, (fileName) => {
1182                                     if (fileName != undefined) {
1183                                         view.webContents.savePage(fileName, 'HTMLComplete', (err) => {
1184                                             if (!err) console.log('Page Save successfully');
1185                                         });
1186                                     }
1187                                 });
1188                             }
1189                         },
1190                         {
1191                             label: lang.window.view.contextMenu.print,
1192                             accelerator: 'CmdOrCtrl+P',
1193                             icon: `${app.getAppPath()}/static/print.png`,
1194                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1195                             click: () => { view.webContents.print(); }
1196                         },
1197                         { type: 'separator' },
1198                         {
1199                             label: lang.window.view.contextMenu.viewSource,
1200                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1201                             click: () => { this.addView(windowId, `view-source:${view.webContents.getURL()}`, true); }
1202                         },
1203                         {
1204                             label: lang.window.view.contextMenu.devTool,
1205                             accelerator: 'CmdOrCtrl+Shift+I',
1206                             enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
1207                             click: () => {
1208                                 if (view.webContents.isDevToolsOpened())
1209                                     view.webContents.devToolsWebContents.focus();
1210                                 else
1211                                     view.webContents.openDevTools();
1212                             }
1213                         }
1214                     ]
1215                 );
1216             }
1217
1218             menu.popup();
1219         });
1220
1221         view.webContents.on('before-input-event', (e, input) => {
1222             if (view.isDestroyed()) return;
1223             console.log((input.control || (platform.isDarwin && input.meta)) || input.alt)
1224             window.webContents.setIgnoreMenuShortcuts(true);
1225
1226             if (input.control) {
1227                 if (input.shift && input.key == 'I') {
1228                     e.preventDefault();
1229                     window.webContents.setIgnoreMenuShortcuts(true);
1230                     view.webContents.setIgnoreMenuShortcuts(true);
1231
1232                     if (view.webContents.isDevToolsOpened()) {
1233                         view.webContents.devToolsWebContents.focus();
1234                     } else {
1235                         view.webContents.openDevTools();
1236                     }
1237                 } else if (input.shift && input.key == 'R') {
1238                     e.preventDefault();
1239                     window.webContents.setIgnoreMenuShortcuts(true);
1240                     view.webContents.setIgnoreMenuShortcuts(true);
1241
1242                     view.webContents.reloadIgnoringCache();
1243                 } else if (input.key == 'T') {
1244                     e.preventDefault();
1245                     window.webContents.setIgnoreMenuShortcuts(true);
1246                     // view.webContents.setIgnoreMenuShortcuts(true);
1247
1248                     console.log('test');
1249                     this.addView(windowId, config.get('homePage.defaultPage'), true);
1250                 }
1251             } else if (input.alt) {
1252                 if (input.key == 'ArrowLeft') {
1253                     e.preventDefault();
1254                     // window.webContents.setIgnoreMenuShortcuts(true);
1255                     view.webContents.setIgnoreMenuShortcuts(true);
1256
1257                     if (view.webContents.canGoBack())
1258                         view.webContents.goBack();
1259                 } else if (input.key == 'ArrowRight') {
1260                     e.preventDefault();
1261                     // window.webContents.setIgnoreMenuShortcuts(true);
1262                     view.webContents.setIgnoreMenuShortcuts(true);
1263
1264                     if (view.webContents.canGoForward())
1265                         view.webContents.goForward();
1266                 }
1267             } else {
1268                 if (input.key == 'F11') {
1269                     e.preventDefault();
1270                     window.webContents.setIgnoreMenuShortcuts(true);
1271                     view.webContents.setIgnoreMenuShortcuts(true);
1272
1273                     window.setFullScreen(!window.isFullScreen());
1274                     this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1275                 }
1276             }
1277         });
1278
1279         view.webContents.session.on('will-download', (event, item, webContents) => {
1280             const str = this.getRandString(12);
1281             db.downloads.find({ id: str }, (err, docs) => {
1282                 if (docs < 1)
1283                     db.downloads.insert({ id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() });
1284             });
1285             item.on('updated', (e, state) => {
1286                 db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
1287             });
1288
1289             item.once('done', (e, state) => {
1290                 const filePath = item.getSavePath();
1291                 db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
1292                 if (state === 'completed') {
1293                     window.webContents.send(`notification-${windowId}`, { id: id, content: `${item.getFilename()} のダウンロードが完了しました。` });
1294
1295                     if (!Notification.isSupported()) return;
1296                     const notify = new Notification({
1297                         icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
1298                         title: 'ダウンロード完了',
1299                         body: `${item.getFilename()} のダウンロードが完了しました。\n詳細はここをクリックしてください。`
1300                     });
1301
1302                     notify.show();
1303
1304
1305                     notify.on('click', (e) => {
1306                         if (filePath !== undefined)
1307                             shell.openItem(filePath);
1308                     });
1309                 } else {
1310                     console.log(`Download failed: ${state}`);
1311                 }
1312             });
1313         });
1314
1315         view.webContents.loadURL(url);
1316
1317         if (views[windowId] == undefined)
1318             views[windowId] = [];
1319         views[windowId].push({ id, view, isNotificationBar: false });
1320         console.log(views);
1321
1322         if (isActive) {
1323             window.webContents.send(`browserview-set-${windowId}`, { id: id });
1324             window.setBrowserView(view);
1325         }
1326
1327         this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1328         this.getViews(windowId);
1329     }
1330
1331     getRandString = (length) => {
1332         const char = 'abcdefghijklmnopqrstuvwxyz0123456789';
1333         const charLength = char.length;
1334
1335         let str = '';
1336         for (var i = 0; i < length; i++) {
1337             str += char[Math.floor(Math.random() * charLength)];
1338         }
1339
1340         return str;
1341     }
1342 }