OSDN Git Service

v1.4.7
[serene/MyBrowser.git] / app / electron / WindowManager.js
1 const { app, ipcMain, protocol, session, BrowserWindow, BrowserView, Menu, nativeImage, clipboard, dialog, Notification } = require('electron');
2 const path = require('path');
3 const fs = require('fs');
4 const url = require('url');
5 const os = require('os');
6
7 const localShortcut = require("electron-localshortcut");
8
9 const Config = require('electron-store');
10 const config = new Config({
11     defaults: {
12         design: {
13             homeButton: false,
14             darkTheme: false,
15             theme: 'default'
16         },
17         homePage: {
18             defaultPage: 'my://newtab',
19             defaultEngine: 'Google',
20             searchEngines: [
21                 {
22                     name: 'Google',
23                     url: 'https://www.google.com/search?q=%s'
24                 },
25                 {
26                     name: 'Bing',
27                     url: 'https://www.bing.com/search?q=%s'
28                 },
29                 {
30                     name: 'Yahoo! Japan',
31                     url: 'https://search.yahoo.co.jp/search?p=%s'
32                 },
33                 {
34                     name: 'goo',
35                     url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
36                 },
37                 {
38                     name: 'Google Translate',
39                     url: 'https://translate.google.com/?text=%s'
40                 },
41                 {
42                     name: 'Youtube',
43                     url: 'https://www.youtube.com/results?search_query=%s'
44                 },
45                 {
46                     name: 'Twitter',
47                     url: 'https://www.twitter.com/search?q=%s'
48                 },
49                 {
50                     name: 'GitHub',
51                     url: 'https://github.com/search?q=%s'
52                 }
53             ]
54         },
55         adBlocker: true,
56         window: {
57             isCustomTitlebar: true,
58             isMaximized: false,
59             bounds: {
60                 width: 1100,
61                 height: 680
62             }
63         }
64     },
65 });
66
67 const Datastore = require('nedb');
68 let db = {};
69
70 db.history = new Datastore({
71     filename: path.join(app.getPath('userData'), 'Files', 'History.db'),
72     autoload: true,
73     timestampData: true
74 });
75 db.bookmark = new Datastore({
76     filename: path.join(app.getPath('userData'), 'Files', 'Bookmark.db'),
77     autoload: true,
78     timestampData: true
79 });
80
81 const { loadFilters, updateFilters, removeAds } = require('./AdBlocker');
82
83 let floatingWindows = [];
84 let views = [];
85 let tabCount = 0;
86
87 getBaseWindow = (width = 1100, height = 680, minWidth = 320, minHeight = 200, x, y, frame = false) => {
88     return new BrowserWindow({
89         width, height, minWidth, minHeight, x, y, 'titleBarStyle': 'hidden', frame, fullscreenable: true,
90         webPreferences: {
91             nodeIntegration: true,
92             webviewTag: true,
93             plugins: true,
94             experimentalFeatures: true,
95             contextIsolation: false,
96         }
97     });
98 }
99
100 registerProtocols = () => {
101     protocol.isProtocolHandled('my', (handled) => {
102         console.log(handled);
103         if (!handled) {
104             protocol.registerFileProtocol('my', (request, callback) => {
105                 const parsed = url.parse(request.url);
106
107                 if (parsed.hostname.endsWith('.css') || parsed.hostname.endsWith('.js')) {
108                     return callback({
109                         path: path.join(app.getAppPath(), 'pages', parsed.hostname),
110                     });
111                 } else {
112                     return callback({
113                         path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
114                     });
115                 }
116             }, (error) => {
117                 if (error) console.error('Failed to register protocol: ' + error);
118             });
119         }
120     });
121 }
122
123 registerProtocolWithPrivateMode = (windowId) => {
124     session.fromPartition(windowId).protocol.registerFileProtocol('my', (request, callback) => {
125         const parsed = url.parse(request.url);
126
127         if (parsed.hostname.endsWith('.css') || parsed.hostname.endsWith('.js')) {
128             return callback({
129                 path: path.join(app.getAppPath(), 'pages', parsed.hostname),
130             });
131         } else {
132             return callback({
133                 path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
134             });
135         }
136     }, (error) => {
137         if (error) console.error('Failed to register protocol: ' + error);
138     });
139 }
140
141 module.exports = class WindowManager {
142     constructor() {
143         this.windows = new Map();
144
145         ipcMain.on('window-add', (e, args) => {
146             this.addWindow(args.isPrivate);
147         });
148
149         ipcMain.on('update-filters', (e, args) => {
150             updateFilters();
151         });
152
153         ipcMain.on('data-history-get', (e, args) => {
154             db.history.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
155                 e.sender.send('data-history-get', { historys: docs });
156             });
157         });
158
159         ipcMain.on('data-history-clear', (e, args) => {
160             db.history.remove({}, { multi: true });
161         });
162
163         ipcMain.on('data-bookmark-get', (e, args) => {
164             db.bookmark.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
165                 e.sender.send('data-bookmark-get', { bookmarks: docs });
166             });
167         });
168
169         ipcMain.on('data-bookmark-clear', (e, args) => {
170             db.bookmark.remove({}, { multi: true });
171         });
172
173         ipcMain.on('clear-browsing-data', () => {
174             const ses = session.defaultSession;
175             ses.clearCache((err) => {
176                 if (err) log.error(err);
177             });
178
179             ses.clearStorageData({
180                 storages: [
181                     'appcache',
182                     'cookies',
183                     'filesystem',
184                     'indexdb',
185                     'localstorage',
186                     'shadercache',
187                     'websql',
188                     'serviceworkers',
189                     'cachestorage',
190                 ],
191             });
192
193             config.clear();
194             db.history.remove({}, { multi: true });
195             db.bookmark.remove({}, { multi: true });
196         });
197     }
198
199     addWindow = (isPrivate = false) => {
200         registerProtocols();
201         loadFilters();
202
203         const { width, height, x, y } = config.get('window.bounds');
204         const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 320, 200, x, y, !config.get('window.isCustomTitlebar'));
205
206         const id = (!isPrivate ? window.id : `private-${window.id}`);
207
208         config.get('window.isMaximized') && window.maximize();
209
210         const startUrl = process.env.ELECTRON_START_URL || url.format({
211             pathname: path.join(__dirname, '/../build/index.html'), // 警告:このファイルを移動する場合ここの相対パスの指定に注意してください
212             protocol: 'file:',
213             slashes: true,
214             hash: `/window/${id}`,
215         });
216
217         window.loadURL(startUrl);
218
219         localShortcut.register(window, 'CmdOrCtrl+Shift+I', () => {
220             if (window.getBrowserView() == undefined) return;
221             const view = window.getBrowserView();
222
223             if (view.webContents.isDevToolsOpened()) {
224                 view.webContents.devToolsWebContents.focus();
225             } else {
226                 view.webContents.openDevTools();
227             }
228         });
229
230         localShortcut.register(window, 'CmdOrCtrl+R', () => {
231             if (window.getBrowserView() == undefined) return;
232             const view = window.getBrowserView();
233
234             view.webContents.reload();
235         });
236
237         localShortcut.register(window, 'CmdOrCtrl+Shift+R', () => {
238             if (window.getBrowserView() == undefined) return;
239             const view = window.getBrowserView();
240
241             view.webContents.reloadIgnoringCache();
242         });
243
244         window.on('closed', () => {
245             this.windows.delete(id);
246         });
247
248         ['resize', 'move'].forEach(ev => {
249             window.on(ev, () => {
250                 config.set('window.isMaximized', window.isMaximized());
251                 config.set('window.bounds', window.getBounds());
252             })
253         });
254
255         window.on('close', (e) => {
256             for (var i = 0; i < views.length; i++) {
257                 if (views[i].windowId == id) {
258                     views.splice(i, 1);
259                 }
260             }
261         });
262
263         window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
264         window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
265         window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
266         window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
267         window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
268         window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
269
270         // registerProtocols();
271         this.registerListeners(id);
272
273         this.windows.set(id, window);
274     }
275
276     registerListeners = (id) => {
277         ipcMain.on(`browserview-add-${id}`, (e, args) => {
278             this.addView(id, args.url, args.isActive);
279         });
280
281         ipcMain.on(`browserview-remove-${id}`, (e, args) => {
282             this.removeView(id, args.id);
283         });
284
285         ipcMain.on(`browserview-select-${id}`, (e, args) => {
286             this.selectView(id, args.id);
287         });
288
289         ipcMain.on(`browserview-get-${id}`, (e, args) => {
290             let datas = [];
291             for (var i = 0; i < views.length; i++) {
292                 if (views[i].windowId == id) {
293                     const url = views[i].view.webContents.getURL();
294                     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}` });
295                 }
296             }
297             e.sender.send(`browserview-get-${id}`, { views: datas });
298         });
299
300         ipcMain.on(`browserview-goBack-${id}`, (e, args) => {
301             views.filter(function (view, i) {
302                 if (view.id == args.id) {
303                     let webContents = views[i].view.webContents;
304                     if (webContents.canGoBack())
305                         webContents.goBack();
306                 }
307             });
308         });
309
310         ipcMain.on(`browserview-goForward-${id}`, (e, args) => {
311             views.filter(function (view, i) {
312                 if (view.id == args.id) {
313                     let webContents = views[i].view.webContents;
314                     if (webContents.canGoForward())
315                         webContents.goForward();
316                 }
317             });
318         });
319
320         ipcMain.on(`browserview-reload-${id}`, (e, args) => {
321             views.filter(function (view, i) {
322                 if (view.id == args.id) {
323                     let webContents = views[i].view.webContents;
324                     webContents.reload();
325                 }
326             });
327         });
328
329         ipcMain.on(`browserview-stop-${id}`, (e, args) => {
330             views.filter(function (view, i) {
331                 if (view.id == args.id) {
332                     let webContents = views[i].view.webContents;
333                     webContents.stop();
334                 }
335             });
336         });
337
338         ipcMain.on(`browserview-goHome-${id}`, (e, args) => {
339             views.filter(function (view, i) {
340                 if (view.id == args.id) {
341                     let webContents = views[i].view.webContents;
342                     webContents.loadURL(config.get('homePage.defaultPage'));
343                 }
344             });
345         });
346
347         ipcMain.on(`browserview-loadURL-${id}`, (e, args) => {
348             views.filter(function (view, i) {
349                 if (view.id == args.id) {
350                     let webContents = views[i].view.webContents;
351                     webContents.loadURL(args.url);
352                 }
353             });
354         });
355
356         ipcMain.on(`browserview-loadFile-${id}`, (e, args) => {
357             views.filter(function (view, i) {
358                 if (view.id == args.id) {
359                     let webContents = views[i].view.webContents;
360                     webContents.loadFile(args.url);
361                 }
362             });
363         });
364
365         ipcMain.on(`data-bookmark-add-${id}`, (e, args) => {
366             views.filter((view, i) => {
367                 if (view.id == args.id) {
368                     let v = views[i].view;
369                     db.bookmark.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
370                     this.updateBookmarkState(id, args.id, v);
371                 }
372             });
373         });
374
375         ipcMain.on(`data-bookmark-remove-${id}`, (e, args) => {
376             views.filter((view, i) => {
377                 if (view.id == args.id) {
378                     let v = views[i].view;
379                     db.bookmark.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
380                     this.updateBookmarkState(id, args.id, v);
381                 }
382             });
383         });
384
385         ipcMain.on(`data-bookmark-has-${id}`, (e, args) => {
386             views.filter((view, i) => {
387                 if (view.id == args.id) {
388                     let v = views[i].view;
389                     db.bookmark.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
390                         e.sender.send(`data-bookmark-has-${id}`, { isBookmarked: (docs.length > 0 ? true : false) });
391                     });
392                 }
393             });
394         });
395     }
396
397     updateNavigationState = (windowId, id, view) => {
398         const window = this.windows.get(windowId);
399         window.webContents.send(`update-navigation-state-${windowId}`, {
400             id: id,
401             canGoBack: view.webContents.canGoBack(),
402             canGoForward: view.webContents.canGoForward(),
403         });
404     }
405
406     updateBookmarkState = (windowId, id, view) => {
407         const window = this.windows.get(windowId);
408         db.bookmark.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
409             window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), isBookmarked: (docs.length > 0 ? true : false) });
410         });
411     }
412
413     fixBounds = (windowId, isFloating = false) => {
414         const window = this.windows.get(windowId);
415
416         if (window.getBrowserView() == undefined) return;
417         const view = window.getBrowserView();
418
419         const { width, height } = window.getContentBounds();
420
421         view.setAutoResize({ width: true, height: true });
422         if (isFloating) {
423             window.setMinimizable(false);
424             window.setMaximizable(false);
425             window.setAlwaysOnTop(true);
426             window.setVisibleOnAllWorkspaces(true);
427             view.setBounds({
428                 x: 1,
429                 y: 1,
430                 width: width - 2,
431                 height: height - 2,
432             });
433         } else {
434             window.setMinimizable(true);
435             window.setMaximizable(true);
436             window.setAlwaysOnTop(false);
437             window.setVisibleOnAllWorkspaces(false);
438             if (window.isFullScreen()) {
439                 view.setBounds({
440                     x: 0,
441                     y: 0,
442                     width: width,
443                     height: height,
444                 });
445             } else {
446                 view.setBounds({
447                     x: 1,
448                     y: 73 + 1,
449                     width: width - 2,
450                     height: window.isMaximized() ? height - 73 : (height - 73) - 2,
451                 });
452             }
453         }
454         view.setAutoResize({ width: true, height: true });
455     }
456
457     addView = (windowId, url, isActive) => {
458         const id = tabCount++;
459         this.addTab(windowId, id, url, isActive);
460     }
461
462     removeView = (windowId, id) => {
463         views.filter((view, i) => {
464             if (windowId == view.windowId && id == view.id) {
465                 views.splice(i, 1);
466             }
467         });
468     }
469
470     selectView = (windowId, id) => {
471         const window = this.windows.get(windowId);
472         views.filter((view, i) => {
473             if (windowId == view.windowId && id == view.id) {
474                 window.setBrowserView(views[i].view);
475                 window.setTitle(views[i].view.webContents.getTitle());
476                 window.webContents.send(`browserview-set-${windowId}`, { id: id });
477                 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
478             }
479         });
480     }
481
482     getViews = (windowId) => {
483         let datas = [];
484         for (var i = 0; i < views.length; i++) {
485             if (views[i].windowId == windowId) {
486                 const url = views[i].view.webContents.getURL();
487                 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}` });
488             }
489         }
490         const window = this.windows.get(windowId);
491         window.webContents.send(`browserview-get-${windowId}`, { views: datas });
492     }
493
494     addTab = (windowId, id, url = config.get('homePage.defaultPage'), isActive = true) => {
495         const window = this.windows.get(windowId);
496
497         const view = new BrowserView({
498             webPreferences: {
499                 nodeIntegration: false,
500                 contextIsolation: false,
501                 plugins: true,
502                 experimentalFeatures: true,
503                 safeDialogs: true,
504                 safeDialogsMessage: '今後このページではダイアログを表示しない',
505                 ...(String(windowId).startsWith('private') && { partition: windowId }),
506                 preload: require.resolve('./Preload')
507             }
508         });
509
510         const defaultUserAgent = view.webContents.getUserAgent();
511
512         if (String(windowId).startsWith('private')) {
513             registerProtocolWithPrivateMode(windowId);
514         }
515
516         view.webContents.on('did-start-loading', () => {
517             if (view.isDestroyed()) return;
518
519             window.webContents.send(`browserview-start-loading-${windowId}`, { id: id });
520         });
521         view.webContents.on('did-stop-loading', () => {
522             if (view.isDestroyed()) return;
523
524             window.webContents.send(`browserview-stop-loading-${windowId}`, { id: id });
525         });
526
527         view.webContents.on('did-finish-load', (e) => {
528             if (view.isDestroyed()) return;
529
530             if (String(windowId).startsWith('private'))
531                 view.webContents.setUserAgent(defaultUserAgent + ' PrivMode');
532
533             window.setTitle(view.webContents.getTitle());
534             this.updateBookmarkState(windowId, id, view);
535
536             this.updateNavigationState(windowId, id, view);
537         });
538
539         view.webContents.on('did-start-navigation', (e) => {
540             if (view.isDestroyed()) return;
541
542             const url = view.webContents.getURL();
543
544             if (config.get('adBlocker'))
545                 removeAds(url, view.webContents);
546
547             this.updateNavigationState(windowId, id, view);
548         });
549
550         view.webContents.on('page-title-updated', (e) => {
551             if (view.isDestroyed()) return;
552
553             window.setTitle(view.webContents.getTitle());
554             this.updateBookmarkState(windowId, id, view);
555
556             if (!String(windowId).startsWith('private') && !view.webContents.getURL().startsWith('my://'))
557                 db.history.insert({ title: view.webContents.getTitle(), url: view.webContents.getURL() });
558
559             this.updateNavigationState(windowId, id, view);
560         });
561
562         view.webContents.on('page-favicon-updated', (e, favicons) => {
563             if (view.isDestroyed()) return;
564
565             window.setTitle(view.webContents.getTitle());
566             db.bookmark.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
567                 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) });
568             });
569
570             this.updateNavigationState(windowId, id, view);
571         });
572
573         view.webContents.on('did-change-theme-color', (e, color) => {
574             if (view.isDestroyed()) return;
575
576             window.setTitle(view.webContents.getTitle());
577             db.bookmark.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
578                 window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), color: color, isBookmarked: (docs.length > 0 ? true : false) });
579             });
580         });
581
582         view.webContents.on('new-window', (e, url) => {
583             if (view.isDestroyed()) return;
584
585             e.preventDefault();
586             this.addView(windowId, url, true);
587         });
588
589         view.webContents.on('context-menu', (e, params) => {
590             if (view.isDestroyed()) return;
591
592             const menu = Menu.buildFromTemplate(
593                 [
594                     ...(params.linkURL !== '' ?
595                         [
596                             {
597                                 label: '新しいタブで開く',
598                                 click: () => {
599                                     this.addView(windowId, params.linkURL, true);
600                                 }
601                             },
602                             {
603                                 label: '新しいウィンドウで開く',
604                                 enabled: false,
605                                 click: () => { view.webContents.openDevTools(); }
606                             },
607                             {
608                                 label: 'プライベート ウィンドウで開く',
609                                 enabled: false,
610                                 click: () => { view.webContents.openDevTools(); }
611                             },
612                             { type: 'separator' },
613                             {
614                                 label: 'リンクをコピー',
615                                 accelerator: 'CmdOrCtrl+C',
616                                 click: () => {
617                                     clipboard.clear();
618                                     clipboard.writeText(params.linkURL);
619                                 }
620                             },
621                             { type: 'separator' }
622                         ] : []),
623                     ...(params.hasImageContents ?
624                         [
625                             {
626                                 label: '新しいタブで画像を開く',
627                                 click: () => {
628                                     this.addView(windowId, params.srcURL, true);
629                                 }
630                             },
631                             {
632                                 label: '画像をコピー',
633                                 click: () => {
634                                     const img = nativeImage.createFromDataURL(params.srcURL);
635
636                                     clipboard.clear();
637                                     clipboard.writeImage(img);
638                                 }
639                             },
640                             {
641                                 label: '画像アドレスをコピー',
642                                 click: () => {
643                                     clipboard.clear();
644                                     clipboard.writeText(params.srcURL);
645                                 }
646                             },
647                             { type: 'separator' }
648                         ] : []),
649                     ...(params.isEditable ?
650                         [
651                             {
652                                 label: '元に戻す',
653                                 accelerator: 'CmdOrCtrl+Z',
654                                 enabled: params.editFlags.canUndo,
655                                 click: () => { view.webContents.undo(); }
656                             },
657                             {
658                                 label: 'やり直す',
659                                 accelerator: 'CmdOrCtrl+Y',
660                                 enabled: params.editFlags.canRedo,
661                                 click: () => { view.webContents.redo(); }
662                             },
663                             { type: 'separator' },
664                             {
665                                 label: '切り取り',
666                                 accelerator: 'CmdOrCtrl+X',
667                                 enabled: params.editFlags.canCut,
668                                 click: () => { view.webContents.cut(); }
669                             },
670                             {
671                                 label: 'コピー',
672                                 accelerator: 'CmdOrCtrl+C',
673                                 enabled: params.editFlags.canCopy,
674                                 click: () => { view.webContents.copy(); }
675                             },
676                             {
677                                 label: '貼り付け',
678                                 accelerator: 'CmdOrCtrl+V',
679                                 enabled: params.editFlags.canPaste,
680                                 click: () => { view.webContents.paste(); }
681                             },
682                             { type: 'separator' },
683                             {
684                                 label: 'すべて選択',
685                                 accelerator: 'CmdOrCtrl+A',
686                                 enabled: params.editFlags.canSelectAll,
687                                 click: () => { view.webContents.selectAll(); }
688                             },
689                             { type: 'separator' }
690                         ] : []),
691                     ...(params.selectionText !== '' && !params.isEditable ?
692                         [
693                             {
694                                 label: 'コピー',
695                                 accelerator: 'CmdOrCtrl+C',
696                                 click: () => { view.webContents.copy(); }
697                             },
698                             {
699                                 label: `Googleで「${params.selectionText}」を検索`,
700                                 click: () => {
701                                     this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
702                                 }
703                             },
704                             { type: 'separator' }
705                         ] : []),
706                     {
707                         label: '戻る',
708                         accelerator: 'Alt+Left',
709                         enabled: view.webContents.canGoBack(),
710                         click: () => { view.webContents.goBack(); }
711                     },
712                     {
713                         label: '進む',
714                         accelerator: 'Alt+Right',
715                         enabled: view.webContents.canGoForward(),
716                         click: () => { view.webContents.goForward(); }
717                     },
718                     {
719                         label: '再読み込み',
720                         accelerator: 'CmdOrCtrl+R',
721                         click: () => { view.webContents.reload(); }
722                     },
723                     { type: 'separator' },
724                     {
725                         label: 'Floating Window (Beta)',
726                         type: 'checkbox',
727                         checked: (floatingWindows.indexOf(windowId) != -1),
728                         enabled: (!window.isFullScreen() && !window.isMaximized()),
729                         click: () => {
730                             if (floatingWindows.indexOf(windowId) != -1) {
731                                 floatingWindows.filter((win, i) => {
732                                     if (windowId == win) {
733                                         floatingWindows.splice(i, 1);
734                                     }
735                                 });
736                             } else {
737                                 floatingWindows.push(windowId);
738                             }
739                             this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
740                         }
741                     },
742                     { type: 'separator' },
743                     {
744                         label: 'ページの保存',
745                         accelerator: 'CmdOrCtrl+S',
746                         enabled: !view.webContents.getURL().startsWith('my://'),
747                         click: () => {
748                             view.webContents.savePage(`${app.getPath('downloads')}/${view.webContents.getTitle()}.html`, 'HTMLComplete', (err) => {
749                                 if (!err) console.log('Page Save successfully');
750                             });
751                         }
752                     },
753                     {
754                         label: '印刷',
755                         accelerator: 'CmdOrCtrl+P',
756                         enabled: !view.webContents.getURL().startsWith('my://'),
757                         click: () => { view.webContents.print(); }
758                     },
759                     { type: 'separator' },
760                     {
761                         label: 'デベロッパーツール',
762                         accelerator: 'CmdOrCtrl+Shift+I',
763                         enabled: !view.webContents.getURL().startsWith('my://'),
764                         click: () => { if (view.webContents.isDevToolsOpened()) { view.webContents.devToolsWebContents.focus(); } else { view.webContents.openDevTools(); } }
765                     }
766                 ]
767             );
768
769             menu.popup();
770         });
771
772         view.webContents.on('before-input-event', (e, input) => {
773             if (view.isDestroyed()) return;
774
775             if ((input.control || input.meta) && input.shift && input.key == 'I') {
776                 e.preventDefault();
777                 if (view.webContents.isDevToolsOpened()) {
778                     view.webContents.devToolsWebContents.focus();
779                 } else {
780                     view.webContents.openDevTools();
781                 }
782             } else if ((input.control || input.meta) && input.key == 'R') {
783                 e.preventDefault();
784                 view.webContents.reload();
785             } else if ((input.control || input.meta) && input.shift && input.key == 'R') {
786                 e.preventDefault();
787                 view.webContents.reloadIgnoringCache();
788             }
789         });
790
791         view.webContents.session.on('will-download', (event, item, webContents) => {
792             item.on('updated', (e, state) => {
793                 if (state === 'interrupted') {
794                     console.log('Download is interrupted but can be resumed')
795                 } else if (state === 'progressing') {
796                     if (item.isPaused()) {
797                         console.log('Download is paused')
798                     } else {
799                         console.log(`Received bytes: ${item.getReceivedBytes()}`)
800                     }
801                 }
802             });
803
804             item.once('done', (e, state) => {
805                 if (state === 'completed') {
806                     console.log('Download successfully')
807                 } else {
808                     console.log(`Download failed: ${state}`)
809                 }
810             });
811         });
812
813         view.webContents.loadURL(url);
814
815         views.push({ windowId, id, view });
816         console.log(views);
817
818         if (isActive) {
819             window.webContents.send(`browserview-set-${windowId}`, { id: id });
820             window.setBrowserView(view);
821         }
822
823         this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
824         this.getViews(windowId);
825     }
826 }