1 const { app, 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');
6 const pkg = require(`${app.getAppPath()}/package.json`);
7 const protocolStr = 'flast';
8 const fileProtocolStr = `${protocolStr}-file`;
10 const platform = require('electron-platform');
11 const localShortcut = require('electron-localshortcut');
13 const Config = require('electron-store');
14 const config = new Config({
22 defaultPage: `${protocolStr}://home`,
23 defaultEngine: 'Google',
27 url: 'https://www.google.com/search?q=%s'
31 url: 'https://www.bing.com/search?q=%s'
35 url: 'https://search.yahoo.co.jp/search?p=%s'
39 url: 'https://search.goo.ne.jp/web.jsp?MT=%s'
43 url: 'https://www.baidu.com/s?wd=%s'
46 name: 'Google Translate',
47 url: 'https://translate.google.com/?text=%s'
51 url: 'https://www.youtube.com/results?search_query=%s'
55 url: 'https://www.twitter.com/search?q=%s'
59 url: 'https://github.com/search?q=%s'
65 isCustomTitlebar: true,
75 const Datastore = require('nedb');
77 db.pageSettings = new Datastore({
78 filename: path.join(app.getPath('userData'), 'Files', 'PageSettings.db'),
83 db.historys = new Datastore({
84 filename: path.join(app.getPath('userData'), 'Files', 'History.db'),
88 db.downloads = new Datastore({
89 filename: path.join(app.getPath('userData'), 'Files', 'Download.db'),
93 db.bookmarks = new Datastore({
94 filename: path.join(app.getPath('userData'), 'Files', 'Bookmarks.db'),
99 db.apps = new Datastore({
100 filename: path.join(app.getPath('userData'), 'Files', 'Apps.db'),
105 const { loadFilters, updateFilters, runAdblockService, removeAds } = require('./AdBlocker');
107 let floatingWindows = [];
110 getBaseWindow = (width = 1100, height = 680, minWidth = 320, minHeight = 200, x, y, frame = false) => {
111 return new BrowserWindow({
112 width, height, minWidth, minHeight, x, y, 'titleBarStyle': 'hidden', frame, fullscreenable: true,
114 icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
116 nodeIntegration: true,
119 experimentalFeatures: true,
120 contextIsolation: false,
125 loadSessionAndProtocol = () => {
126 const ses = session.defaultSession;
128 setPermissionRequestHandler(ses, false);
130 protocol.isProtocolHandled(protocolStr, (handled) => {
132 protocol.registerFileProtocol(protocolStr, (request, callback) => {
133 const parsed = parse(request.url);
136 path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
139 if (error) console.error('Failed to register protocol: ' + error);
144 protocol.isProtocolHandled(fileProtocolStr, (handled) => {
146 protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
147 const parsed = parse(request.url);
150 path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
153 if (error) console.error('Failed to register protocol: ' + error);
159 loadSessionAndProtocolWithPrivateMode = (windowId) => {
160 const ses = session.fromPartition(windowId);
161 ses.setUserAgent(ses.getUserAgent().replace(/ Electron\/[0-9\.]*/g, '') + ' PrivMode');
163 setPermissionRequestHandler(ses, true);
165 ses.protocol.registerFileProtocol(protocolStr, (request, callback) => {
166 const parsed = parse(request.url);
169 path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
172 if (error) console.error('Failed to register protocol: ' + error);
175 ses.protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
176 const parsed = parse(request.url);
179 path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
182 if (error) console.error('Failed to register protocol: ' + error);
186 setPermissionRequestHandler = (ses, isPrivate = false) => {
188 ses.setPermissionRequestHandler((webContents, permission, callback) => {
189 db.pageSettings.findOne({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, (err, doc) => {
190 if (doc != undefined) {
191 if (permission == 'media' && doc.media != undefined && doc.media > -1)
192 return callback(doc.media === 0);
193 if (permission == 'geolocation' && doc.geolocation != undefined && doc.geolocation > -1)
194 return callback(doc.geolocation === 0);
195 if (permission == 'notifications' && doc.notifications != undefined && doc.notifications > -1)
196 return callback(doc.notifications === 0);
197 if (permission == 'midiSysex' && doc.midiSysex != undefined && doc.midiSysex > -1)
198 return callback(doc.midiSysex === 0);
199 if (permission == 'pointerLock' && doc.pointerLock != undefined && doc.pointerLock > -1)
200 return callback(doc.pointerLock === 0);
201 if (permission == 'fullscreen' && doc.fullscreen != undefined && doc.fullscreen > -1)
202 return callback(doc.fullscreen === 0);
203 if (permission == 'openExternal' && doc.openExternal != undefined && doc.openExternal > -1)
204 return callback(doc.openExternal === 0);
206 dialog.showMessageBox({
209 message: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname} が権限を要求しています。`,
210 detail: `要求内容: ${permission}`,
211 checkboxLabel: 'このサイトでは今後も同じ処理をする',
212 checkboxChecked: false,
214 buttons: ['Yes', 'No'],
217 }, (res, checked) => {
218 console.log(res, checked);
220 if (permission == 'media')
221 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, media: res }, { upsert: true });
222 if (permission == 'geolocation')
223 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, geolocation: res }, { upsert: true });
224 if (permission == 'notifications')
225 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, notifications: res }, { upsert: true });
226 if (permission == 'midiSysex')
227 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, midiSysex: res }, { upsert: true });
228 if (permission == 'pointerLock')
229 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, pointerLock: res }, { upsert: true });
230 if (permission == 'fullscreen')
231 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, fullscreen: res }, { upsert: true });
232 if (permission == 'openExternal')
233 db.pageSettings.update({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, { origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}`, openExternal: res }, { upsert: true });
235 return callback(res === 0);
241 ses.setPermissionRequestHandler((webContents, permission, callback) => {
242 dialog.showMessageBox({
245 message: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname} が権限を要求しています。`,
246 detail: `要求内容: ${permission}`,
248 buttons: ['Yes', 'No'],
252 return callback(res === 0);
258 module.exports = class WindowManager {
260 this.windows = new Map();
262 ipcMain.on('window-add', (e, args) => {
263 this.addWindow(args.isPrivate);
266 ipcMain.on('update-filters', (e, args) => {
270 ipcMain.on('data-history-get', (e, args) => {
271 db.historys.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
272 e.sender.send('data-history-get', { historys: docs });
276 ipcMain.on('data-history-clear', (e, args) => {
277 db.historys.remove({}, { multi: true });
280 ipcMain.on('data-downloads-get', (e, args) => {
281 db.downloads.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
282 e.sender.send('data-downloads-get', { downloads: docs });
286 ipcMain.on('data-downloads-clear', (e, args) => {
287 db.downloads.remove({}, { multi: true });
290 ipcMain.on('data-bookmarks-get', (e, args) => {
291 db.bookmarks.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
292 e.sender.send('data-bookmarks-get', { bookmarks: docs });
296 ipcMain.on('data-bookmarks-clear', (e, args) => {
297 db.bookmarks.remove({}, { multi: true });
300 ipcMain.on('data-apps-add', (e, args) => {
301 db.apps.update({ id: args.id }, { id: args.id, name: args.name, description: args.description, url: args.url }, { upsert: true });
303 db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
308 ipcMain.on('data-apps-remove', (e, args) => {
309 db.apps.remove({ id: args.id }, {});
312 ipcMain.on('data-apps-get', (e, args) => {
313 db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
314 e.sender.send('data-apps-get', { apps: docs });
318 ipcMain.on('data-apps-is', (e, args) => {
319 db.apps.find({ id: args.id }).exec((err, docs) => {
320 e.sender.send('data-apps-is', { id: args.id, isInstalled: (docs.length > 0 ? true : false) });
324 ipcMain.on('data-apps-clear', (e, args) => {
325 db.apps.remove({}, { multi: true });
328 ipcMain.on('clear-browsing-data', () => {
329 const ses = session.defaultSession;
330 ses.clearCache((err) => {
331 if (err) log.error(err);
334 ses.clearStorageData({
349 db.historys.remove({}, { multi: true });
350 db.downloads.remove({}, { multi: true });
351 db.bookmarks.remove({}, { multi: true });
352 db.apps.remove({}, { multi: true });
356 addWindow = (isPrivate = false) => {
357 loadSessionAndProtocol();
360 const { width, height, x, y } = config.get('window.bounds');
361 const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 320, 200, x, y, !config.get('window.isCustomTitlebar'));
363 const id = (!isPrivate ? `window-${window.id}` : `private-${window.id}`);
365 config.get('window.isMaximized') && window.maximize();
367 const startUrl = process.env.ELECTRON_START_URL || format({
368 pathname: path.join(__dirname, '/../build/index.html'), // 警告:このファイルを移動する場合ここの相対パスの指定に注意してください
371 hash: `/window/${id}`,
374 window.loadURL(startUrl);
376 window.once('ready-to-show', () => {
380 window.on('closed', () => {
381 this.windows.delete(id);
384 window.on('close', (e) => {
387 config.set('window.isMaximized', window.isMaximized());
388 config.set('window.bounds', window.getBounds());
391 window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
392 window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
393 window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
394 window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
395 window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
396 window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
398 // registerProtocols();
399 this.registerListeners(id);
401 localShortcut.register(window, 'CmdOrCtrl+Shift+I', () => {
402 if (window.getBrowserView() == undefined) return;
403 const view = window.getBrowserView();
405 if (view.webContents.isDevToolsOpened()) {
406 view.webContents.devToolsWebContents.focus();
408 view.webContents.openDevTools();
412 localShortcut.register(window, 'CmdOrCtrl+R', () => {
413 if (window.getBrowserView() == undefined) return;
414 const view = window.getBrowserView();
416 view.webContents.reload();
419 localShortcut.register(window, 'CmdOrCtrl+Shift+R', () => {
420 if (window.getBrowserView() == undefined) return;
421 const view = window.getBrowserView();
423 view.webContents.reloadIgnoringCache();
426 this.windows.set(id, window);
428 if (process.argv != undefined) {
429 window.webContents.send(`tab-add-${id}`, { url: process.argv[process.argv.length - 1] });
433 registerListeners = (id) => {
434 ipcMain.on(`browserview-add-${id}`, (e, args) => {
435 this.addView(id, args.url, args.isActive);
438 ipcMain.on(`browserview-remove-${id}`, (e, args) => {
439 this.removeView(id, args.id);
442 ipcMain.on(`browserview-select-${id}`, (e, args) => {
443 this.selectView(id, args.id);
446 ipcMain.on(`browserview-get-${id}`, (e, args) => {
448 for (var i = 0; i < views[id].length; i++) {
449 const url = views[id][i].view.webContents.getURL();
451 datas.push({ id: views[id][i].view.webContents.id, title: views[id][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
453 e.sender.send(`browserview-get-${id}`, { views: datas });
456 ipcMain.on(`browserview-goBack-${id}`, (e, args) => {
457 views[id].filter(function (view, i) {
458 if (view.view.webContents.id == args.id) {
459 let webContents = views[id][i].view.webContents;
460 if (webContents.canGoBack())
461 webContents.goBack();
466 ipcMain.on(`browserview-goForward-${id}`, (e, args) => {
467 views[id].filter(function (view, i) {
468 if (view.view.webContents.id == args.id) {
469 let webContents = views[id][i].view.webContents;
470 if (webContents.canGoForward())
471 webContents.goForward();
476 ipcMain.on(`browserview-reload-${id}`, (e, args) => {
477 views[id].filter(function (view, i) {
478 if (view.view.webContents.id == args.id) {
479 let webContents = views[id][i].view.webContents;
480 webContents.reload();
485 ipcMain.on(`browserview-stop-${id}`, (e, args) => {
486 views[id].filter(function (view, i) {
487 if (view.view.webContents.id == args.id) {
488 let webContents = views[id][i].view.webContents;
494 ipcMain.on(`browserview-goHome-${id}`, (e, args) => {
495 views[id].filter(function (view, i) {
496 if (view.view.webContents.id == args.id) {
497 let webContents = views[id][i].view.webContents;
498 webContents.loadURL(config.get('homePage.defaultPage'));
503 ipcMain.on(`browserview-loadURL-${id}`, (e, args) => {
504 views[id].filter(function (view, i) {
505 if (view.view.webContents.id == args.id) {
506 let webContents = views[id][i].view.webContents;
507 webContents.loadURL(args.url);
512 ipcMain.on(`browserview-loadFile-${id}`, (e, args) => {
513 views[id].filter(function (view, i) {
514 if (view.view.webContents.id == args.id) {
515 let webContents = views[id][i].view.webContents;
516 webContents.loadFile(args.url);
521 ipcMain.on(`data-bookmark-add-${id}`, (e, args) => {
522 views[id].filter((view, i) => {
523 if (view.view.webContents.id == args.id) {
524 let v = views[id][i].view;
525 db.bookmarks.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
526 this.updateBookmarkState(id, v);
531 ipcMain.on(`data-bookmark-remove-${id}`, (e, args) => {
532 views[id].filter((view, i) => {
533 if (view.view.webContents.id == args.id) {
534 let v = views[id][i].view;
535 db.bookmarks.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
536 this.updateBookmarkState(id, v);
541 ipcMain.on(`data-bookmark-has-${id}`, (e, args) => {
542 views[id].filter((view, i) => {
543 if (view.view.webContents.id == args.id) {
544 let v = views[id][i].view;
545 db.bookmarks.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
546 e.sender.send(`data-bookmark-has-${id}`, { isBookmarked: (docs.length > 0 ? true : false) });
553 getFavicon = (url) => {
554 const parsed = parse(url);
555 return url.startsWith(`${protocolStr}://`) || url.startsWith(`${fileProtocolStr}://`) ? undefined : `https://www.google.com/s2/favicons?domain=${parsed.protocol}//${parsed.hostname}`;
558 updateNavigationState = (id, view) => {
559 const window = this.windows.get(id);
560 window.webContents.send(`update-navigation-state-${id}`, {
561 id: view.webContents.id,
562 canGoBack: view.webContents.canGoBack(),
563 canGoForward: view.webContents.canGoForward(),
567 updateBookmarkState = (id, view) => {
568 const window = this.windows.get(id);
569 db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(id).startsWith('private')) }, (err, docs) => {
570 const url = view.webContents.getURL();
571 window.webContents.send(`browserview-load-${id}`, { id: view.webContents.id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), isBookmarked: (docs.length > 0 ? true : false) });
575 fixBounds = (windowId, isFloating = false) => {
576 const window = this.windows.get(windowId);
578 if (window.getBrowserView() == undefined) return;
579 const view = window.getBrowserView();
581 const { width, height } = window.getContentBounds();
583 view.setAutoResize({ width: true, height: true });
585 window.setMinimizable(false);
586 window.setMaximizable(false);
587 window.setAlwaysOnTop(true);
588 window.setVisibleOnAllWorkspaces(true);
596 window.setMinimizable(true);
597 window.setMaximizable(true);
598 window.setAlwaysOnTop(false);
599 window.setVisibleOnAllWorkspaces(false);
600 if (window.isFullScreen()) {
612 height: window.isMaximized() ? height - 73 : (height - 73) - 2,
616 view.setAutoResize({ width: true, height: true });
619 addView = (id, url, isActive) => {
620 if (String(id).startsWith('private')) {
621 loadSessionAndProtocolWithPrivateMode(id);
624 this.addTab(id, url, isActive);
627 removeView = (windowId, id) => {
628 views[windowId].filter((view, i) => {
629 if (view.view.webContents.id == id) {
632 if (index + 1 < views[windowId].length) {
633 this.selectView2(windowId, index + 1);
634 } else if (index - 1 >= 0) {
635 this.selectView2(windowId, index - 1);
638 views[windowId][index].view.destroy();
639 views[windowId].splice(index, 1);
644 selectView = (windowId, id) => {
645 const window = this.windows.get(windowId);
646 views[windowId].filter((view, i) => {
647 if (id == view.view.webContents.id) {
648 window.setBrowserView(views[windowId][i].view);
649 window.setTitle(views[windowId][i].view.webContents.getTitle());
650 window.webContents.send(`browserview-set-${windowId}`, { id: id });
651 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
656 selectView2 = (windowId, i) => {
657 const window = this.windows.get(windowId);
658 const item = views[windowId][i];
660 window.setBrowserView(item.view);
661 window.setTitle(item.view.webContents.getTitle());
662 window.webContents.send(`browserview-set-${windowId}`, { id: item.id });
663 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
666 getViews = (windowId) => {
668 for (var i = 0; i < views[windowId].length; i++) {
669 const url = views[windowId][i].view.webContents.getURL();
671 datas.push({ id: views[windowId][i].view.webContents.id, title: views[windowId][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
673 const window = this.windows.get(windowId);
674 window.webContents.send(`browserview-get-${windowId}`, { views: datas });
677 addTab = (windowId, url = config.get('homePage.defaultPage'), isActive = true) => {
678 const view = new BrowserView({
680 nodeIntegration: false,
681 contextIsolation: false,
683 experimentalFeatures: true,
685 safeDialogsMessage: '今後このページではダイアログを表示しない',
686 ...(String(windowId).startsWith('private') && { partition: windowId }),
687 preload: require.resolve('./Preload')
691 const window = this.windows.get(windowId);
692 const id = view.webContents.id;
694 runAdblockService(window, windowId, id, view.webContents.session);
696 view.webContents.on('did-start-loading', () => {
697 if (view.isDestroyed()) return;
699 window.webContents.send(`browserview-start-loading-${windowId}`, { id: id });
701 view.webContents.on('did-stop-loading', () => {
702 if (view.isDestroyed()) return;
704 window.webContents.send(`browserview-stop-loading-${windowId}`, { id: id });
707 view.webContents.on('did-finish-load', (e) => {
708 if (view.isDestroyed()) return;
710 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
711 this.updateBookmarkState(windowId, view);
713 this.updateNavigationState(windowId, view);
715 view.webContents.on('did-fail-load', (e, code, description, url, isMainFrame, processId, routingId) => {
716 if (view.isDestroyed()) return;
718 dialog.showMessageBox({message: `${code}: ${description}`});
721 view.webContents.on('did-start-navigation', (e) => {
722 if (view.isDestroyed()) return;
724 const url = view.webContents.getURL();
726 if (config.get('adBlocker') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
727 removeAds(url, view.webContents);
729 this.updateNavigationState(windowId, view);
732 view.webContents.on('page-title-updated', (e) => {
733 if (view.isDestroyed()) return;
735 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
736 this.updateBookmarkState(windowId, view);
738 if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
739 db.historys.insert({ title: view.webContents.getTitle(), url: view.webContents.getURL() });
741 this.updateNavigationState(windowId, view);
744 view.webContents.on('page-favicon-updated', (e, favicons) => {
745 if (view.isDestroyed()) return;
747 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
748 db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
749 const url = view.webContents.getURL();
750 window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), isBookmarked: (docs.length > 0 ? true : false) });
753 this.updateNavigationState(windowId, view);
756 view.webContents.on('did-change-theme-color', (e, color) => {
757 if (view.isDestroyed()) return;
759 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
760 db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
761 window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), color: color, isBookmarked: (docs.length > 0 ? true : false) });
765 view.webContents.on('new-window', (e, url) => {
766 if (view.isDestroyed()) return;
769 this.addView(windowId, url, true);
772 view.webContents.on('certificate-error', (e, url, error, certificate, callback) => {
775 const dlg = dialog.showMessageBox({
778 message: 'この接続ではプライバシーが保護されません',
779 detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
781 buttons: ['続行', 'キャンセル'],
785 // e.preventDefault();
789 view.webContents.on('context-menu', (e, params) => {
790 if (view.isDestroyed()) return;
792 const menu = Menu.buildFromTemplate(
794 ...(params.linkURL !== '' ?
799 this.addView(windowId, params.linkURL, true);
803 label: '新しいウィンドウで開く',
805 click: () => { view.webContents.openDevTools(); }
808 label: 'プライベート ウィンドウで開く',
810 click: () => { view.webContents.openDevTools(); }
812 { type: 'separator' },
815 accelerator: 'CmdOrCtrl+C',
818 clipboard.writeText(params.linkURL);
821 { type: 'separator' }
823 ...(params.hasImageContents ?
826 label: '新しいタブで画像を開く',
828 this.addView(windowId, params.srcURL, true);
834 const img = nativeImage.createFromDataURL(params.srcURL);
837 clipboard.writeImage(img);
844 clipboard.writeText(params.srcURL);
847 { type: 'separator' }
849 ...(params.isEditable ?
853 accelerator: 'CmdOrCtrl+Z',
854 enabled: params.editFlags.canUndo,
855 click: () => { view.webContents.undo(); }
859 accelerator: 'CmdOrCtrl+Y',
860 enabled: params.editFlags.canRedo,
861 click: () => { view.webContents.redo(); }
863 { type: 'separator' },
866 accelerator: 'CmdOrCtrl+X',
867 enabled: params.editFlags.canCut,
868 click: () => { view.webContents.cut(); }
872 accelerator: 'CmdOrCtrl+C',
873 enabled: params.editFlags.canCopy,
874 click: () => { view.webContents.copy(); }
878 accelerator: 'CmdOrCtrl+V',
879 enabled: params.editFlags.canPaste,
880 click: () => { view.webContents.paste(); }
882 { type: 'separator' },
885 accelerator: 'CmdOrCtrl+A',
886 enabled: params.editFlags.canSelectAll,
887 click: () => { view.webContents.selectAll(); }
889 { type: 'separator' }
891 ...(params.selectionText !== '' && !params.isEditable ?
895 accelerator: 'CmdOrCtrl+C',
896 click: () => { view.webContents.copy(); }
899 label: `Googleで「${params.selectionText}」を検索`,
901 this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
904 { type: 'separator' }
908 accelerator: 'Alt+Left',
909 icon: view.webContents.canGoBack() ? `${app.getAppPath()}/static/arrow_back.png` : `${app.getAppPath()}/static/arrow_back_inactive.png`,
910 enabled: view.webContents.canGoBack(),
911 click: () => { view.webContents.goBack(); }
915 accelerator: 'Alt+Right',
916 icon: view.webContents.canGoForward() ? `${app.getAppPath()}/static/arrow_forward.png` : `${app.getAppPath()}/static/arrow_forward_inactive.png`,
917 enabled: view.webContents.canGoForward(),
918 click: () => { view.webContents.goForward(); }
921 label: !view.webContents.isLoadingMainFrame() ? '再読み込み' : '読み込み中止',
922 accelerator: 'CmdOrCtrl+R',
923 icon: !view.webContents.isLoadingMainFrame() ? `${app.getAppPath()}/static/refresh.png` : `${app.getAppPath()}/static/close.png`,
924 click: () => { !view.webContents.isLoadingMainFrame() ? view.webContents.reload() : view.webContents.stop(); }
926 { type: 'separator' },
928 label: 'Floating Window (Beta)',
930 checked: (floatingWindows.indexOf(windowId) != -1),
931 enabled: (!window.isFullScreen() && !window.isMaximized() && config.get('window.isCustomTitlebar')),
933 if (floatingWindows.indexOf(windowId) != -1) {
934 floatingWindows.filter((win, i) => {
935 if (windowId == win) {
936 floatingWindows.splice(i, 1);
940 floatingWindows.push(windowId);
942 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
945 { type: 'separator' },
948 accelerator: 'CmdOrCtrl+S',
949 enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
951 view.webContents.savePage(`${app.getPath('downloads')}/${view.webContents.getTitle()}.html`, 'HTMLComplete', (err) => {
952 if (!err) console.log('Page Save successfully');
958 accelerator: 'CmdOrCtrl+P',
959 icon: `${app.getAppPath()}/static/print.png`,
960 enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
961 click: () => { view.webContents.print(); }
963 { type: 'separator' },
966 accelerator: 'CmdOrCtrl+Shift+I',
967 enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
968 click: () => { if (view.webContents.isDevToolsOpened()) { view.webContents.devToolsWebContents.focus(); } else { view.webContents.openDevTools(); } }
976 view.webContents.on('before-input-event', (e, input) => {
977 if (view.isDestroyed()) return;
979 if (input.control || (platform.isDarwin && input.meta)) {
980 console.log(input.control, input.alt, input.shift, input.key);
982 if (input.shift && input.key == 'I') {
984 if (view.webContents.isDevToolsOpened()) {
985 view.webContents.devToolsWebContents.focus();
987 view.webContents.openDevTools();
989 } else if (input.shift && input.key == 'R') {
991 view.webContents.reloadIgnoringCache();
992 } else if (input.key == 'T') {
994 this.addView(windowId, config.get('homePage.defaultPage'), true)
996 } else if (input.alt) {
997 if (input.key == 'ArrowLeft') {
999 if (view.webContents.canGoBack())
1000 view.webContents.goBack();
1001 } else if (input.key == 'ArrowRight') {
1003 if (view.webContents.canGoForward())
1004 view.webContents.goForward();
1007 if (input.key == 'F11') {
1009 window.setFullScreen(!window.isFullScreen());
1010 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1015 view.webContents.session.on('will-download', (event, item, webContents) => {
1016 const str = this.getRandString(12);
1017 db.downloads.insert({ id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() });
1018 item.on('updated', (e, state) => {
1019 db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
1022 item.once('done', (e, state) => {
1023 db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
1027 view.webContents.loadURL(url);
1029 if (views[windowId] == undefined)
1030 views[windowId] = [];
1031 views[windowId].push({ id, view, isNotificationBar: false });
1035 window.webContents.send(`browserview-set-${windowId}`, { id: id });
1036 window.setBrowserView(view);
1039 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1040 this.getViews(windowId);
1043 getRandString = (length) => {
1044 const char = 'abcdefghijklmnopqrstuvwxyz0123456789';
1045 const charLength = char.length;
1048 for (var i = 0; i < length; i++) {
1049 str += char[Math.floor(Math.random() * charLength)];