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, removeAds } = require('./AdBlocker');
107 let floatingWindows = [];
111 getBaseWindow = (width = 1100, height = 680, minWidth = 320, minHeight = 200, x, y, frame = false) => {
112 return new BrowserWindow({
113 width, height, minWidth, minHeight, x, y, 'titleBarStyle': 'hidden', frame, fullscreenable: true,
115 icon: path.join(app.getAppPath(), 'static', 'app', 'icon.png'),
117 nodeIntegration: true,
120 experimentalFeatures: true,
121 contextIsolation: false,
126 loadSessionAndProtocol = () => {
127 const ses = session.defaultSession;
129 setPermissionRequestHandler(ses, false);
131 protocol.isProtocolHandled(protocolStr, (handled) => {
133 protocol.registerFileProtocol(protocolStr, (request, callback) => {
134 const parsed = parse(request.url);
137 path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
140 if (error) console.error('Failed to register protocol: ' + error);
145 protocol.isProtocolHandled(fileProtocolStr, (handled) => {
147 protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
148 const parsed = parse(request.url);
151 path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
154 if (error) console.error('Failed to register protocol: ' + error);
160 loadSessionAndProtocolWithPrivateMode = (windowId) => {
161 const ses = session.fromPartition(windowId);
162 ses.setUserAgent(ses.getUserAgent().replace(/ Electron\/[0-9\.]*/g, '') + ' PrivMode');
164 setPermissionRequestHandler(ses, true);
166 ses.protocol.registerFileProtocol(protocolStr, (request, callback) => {
167 const parsed = parse(request.url);
170 path: path.join(app.getAppPath(), 'pages', `${parsed.hostname}.html`),
173 if (error) console.error('Failed to register protocol: ' + error);
176 ses.protocol.registerFileProtocol(fileProtocolStr, (request, callback) => {
177 const parsed = parse(request.url);
180 path: path.join(app.getAppPath(), 'pages', 'static', parsed.pathname),
183 if (error) console.error('Failed to register protocol: ' + error);
187 setPermissionRequestHandler = (ses, isPrivate = false) => {
189 ses.setPermissionRequestHandler((webContents, permission, callback) => {
190 db.pageSettings.findOne({ origin: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname}` }, (err, doc) => {
191 if (doc != undefined) {
192 if (permission == 'media' && doc.media != undefined && doc.media > -1)
193 return callback(doc.media === 0);
194 if (permission == 'geolocation' && doc.geolocation != undefined && doc.geolocation > -1)
195 return callback(doc.geolocation === 0);
196 if (permission == 'notifications' && doc.notifications != undefined && doc.notifications > -1)
197 return callback(doc.notifications === 0);
198 if (permission == 'midiSysex' && doc.midiSysex != undefined && doc.midiSysex > -1)
199 return callback(doc.midiSysex === 0);
200 if (permission == 'pointerLock' && doc.pointerLock != undefined && doc.pointerLock > -1)
201 return callback(doc.pointerLock === 0);
202 if (permission == 'fullscreen' && doc.fullscreen != undefined && doc.fullscreen > -1)
203 return callback(doc.fullscreen === 0);
204 if (permission == 'openExternal' && doc.openExternal != undefined && doc.openExternal > -1)
205 return callback(doc.openExternal === 0);
207 dialog.showMessageBox({
210 message: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname} が権限を要求しています。`,
211 detail: `要求内容: ${permission}`,
212 checkboxLabel: 'このサイトでは今後も同じ処理をする',
213 checkboxChecked: false,
215 buttons: ['Yes', 'No'],
218 }, (res, checked) => {
219 console.log(res, checked);
221 if (permission == 'media')
222 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 });
223 if (permission == 'geolocation')
224 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 });
225 if (permission == 'notifications')
226 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 });
227 if (permission == 'midiSysex')
228 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 });
229 if (permission == 'pointerLock')
230 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 });
231 if (permission == 'fullscreen')
232 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 });
233 if (permission == 'openExternal')
234 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 });
236 return callback(res === 0);
242 ses.setPermissionRequestHandler((webContents, permission, callback) => {
243 dialog.showMessageBox({
246 message: `${parse(webContents.getURL()).protocol}//${parse(webContents.getURL()).hostname} が権限を要求しています。`,
247 detail: `要求内容: ${permission}`,
249 buttons: ['Yes', 'No'],
253 return callback(res === 0);
259 module.exports = class WindowManager {
261 this.windows = new Map();
263 ipcMain.on('window-add', (e, args) => {
264 this.addWindow(args.isPrivate);
267 ipcMain.on('update-filters', (e, args) => {
271 ipcMain.on('data-history-get', (e, args) => {
272 db.historys.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
273 e.sender.send('data-history-get', { historys: docs });
277 ipcMain.on('data-history-clear', (e, args) => {
278 db.historys.remove({}, { multi: true });
281 ipcMain.on('data-downloads-get', (e, args) => {
282 db.downloads.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
283 e.sender.send('data-downloads-get', { downloads: docs });
287 ipcMain.on('data-downloads-clear', (e, args) => {
288 db.downloads.remove({}, { multi: true });
291 ipcMain.on('data-bookmarks-get', (e, args) => {
292 db.bookmarks.find({ isPrivate: args.isPrivate }).sort({ createdAt: -1 }).exec((err, docs) => {
293 e.sender.send('data-bookmarks-get', { bookmarks: docs });
297 ipcMain.on('data-bookmarks-clear', (e, args) => {
298 db.bookmarks.remove({}, { multi: true });
301 ipcMain.on('data-apps-add', (e, args) => {
302 db.apps.update({ id: args.id }, { id: args.id, name: args.name, description: args.description, url: args.url }, { upsert: true });
304 db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
309 ipcMain.on('data-apps-remove', (e, args) => {
310 db.apps.remove({ id: args.id }, {});
313 ipcMain.on('data-apps-get', (e, args) => {
314 db.apps.find({}).sort({ createdAt: -1 }).exec((err, docs) => {
315 e.sender.send('data-apps-get', { apps: docs });
319 ipcMain.on('data-apps-is', (e, args) => {
320 db.apps.find({ id: args.id }).exec((err, docs) => {
321 e.sender.send('data-apps-is', { id: args.id, isInstalled: (docs.length > 0 ? true : false) });
325 ipcMain.on('data-apps-clear', (e, args) => {
326 db.apps.remove({}, { multi: true });
329 ipcMain.on('clear-browsing-data', () => {
330 const ses = session.defaultSession;
331 ses.clearCache((err) => {
332 if (err) log.error(err);
335 ses.clearStorageData({
350 db.historys.remove({}, { multi: true });
351 db.downloads.remove({}, { multi: true });
352 db.bookmarks.remove({}, { multi: true });
353 db.apps.remove({}, { multi: true });
357 addWindow = (isPrivate = false) => {
358 loadSessionAndProtocol();
361 const { width, height, x, y } = config.get('window.bounds');
362 const window = getBaseWindow(config.get('window.isMaximized') ? 1110 : width, config.get('window.isMaximized') ? 680 : height, 320, 200, x, y, !config.get('window.isCustomTitlebar'));
364 const id = (!isPrivate ? `window-${window.id}` : `private-${window.id}`);
366 config.get('window.isMaximized') && window.maximize();
368 const startUrl = process.env.ELECTRON_START_URL || format({
369 pathname: path.join(__dirname, '/../build/index.html'), // 警告:このファイルを移動する場合ここの相対パスの指定に注意してください
372 hash: `/window/${id}`,
375 window.loadURL(startUrl);
377 window.once('ready-to-show', () => {
381 window.on('closed', () => {
382 this.windows.delete(id);
386 ['resize', 'move'].forEach(ev => {
387 window.on(ev, () => {
388 config.set('window.isMaximized', window.isMaximized());
389 config.set('window.bounds', window.getBounds());
394 window.on('close', (e) => {
397 config.set('window.isMaximized', window.isMaximized());
398 config.set('window.bounds', window.getBounds());
401 window.on('maximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
402 window.on('unmaximize', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
403 window.on('enter-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
404 window.on('leave-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
405 window.on('enter-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
406 window.on('leave-html-full-screen', this.fixBounds.bind(this, id, (floatingWindows.indexOf(id) != -1)));
408 // registerProtocols();
409 this.registerListeners(id);
411 localShortcut.register(window, 'CmdOrCtrl+Shift+I', () => {
412 if (window.getBrowserView() == undefined) return;
413 const view = window.getBrowserView();
415 if (view.webContents.isDevToolsOpened()) {
416 view.webContents.devToolsWebContents.focus();
418 view.webContents.openDevTools();
422 localShortcut.register(window, 'CmdOrCtrl+R', () => {
423 if (window.getBrowserView() == undefined) return;
424 const view = window.getBrowserView();
426 view.webContents.reload();
429 localShortcut.register(window, 'CmdOrCtrl+Shift+R', () => {
430 if (window.getBrowserView() == undefined) return;
431 const view = window.getBrowserView();
433 view.webContents.reloadIgnoringCache();
436 this.windows.set(id, window);
438 if (process.argv != undefined) {
439 window.webContents.send(`tab-add-${id}`, { url: process.argv[process.argv.length - 1] });
443 registerListeners = (id) => {
444 ipcMain.on(`browserview-add-${id}`, (e, args) => {
445 this.addView(id, args.url, args.isActive);
448 ipcMain.on(`browserview-remove-${id}`, (e, args) => {
449 this.removeView(id, args.id);
452 ipcMain.on(`browserview-select-${id}`, (e, args) => {
453 this.selectView(id, args.id);
456 ipcMain.on(`browserview-get-${id}`, (e, args) => {
458 for (var i = 0; i < views[id].length; i++) {
459 const url = views[id][i].view.webContents.getURL();
461 datas.push({ id: views[id][i].id, title: views[id][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
463 e.sender.send(`browserview-get-${id}`, { views: datas });
466 ipcMain.on(`browserview-goBack-${id}`, (e, args) => {
467 views[id].filter(function (view, i) {
468 if (view.id == args.id) {
469 let webContents = views[id][i].view.webContents;
470 if (webContents.canGoBack())
471 webContents.goBack();
476 ipcMain.on(`browserview-goForward-${id}`, (e, args) => {
477 views[id].filter(function (view, i) {
478 if (view.id == args.id) {
479 let webContents = views[id][i].view.webContents;
480 if (webContents.canGoForward())
481 webContents.goForward();
486 ipcMain.on(`browserview-reload-${id}`, (e, args) => {
487 views[id].filter(function (view, i) {
488 if (view.id == args.id) {
489 let webContents = views[id][i].view.webContents;
490 webContents.reload();
495 ipcMain.on(`browserview-stop-${id}`, (e, args) => {
496 views[id].filter(function (view, i) {
497 if (view.id == args.id) {
498 let webContents = views[id][i].view.webContents;
504 ipcMain.on(`browserview-goHome-${id}`, (e, args) => {
505 views[id].filter(function (view, i) {
506 if (view.id == args.id) {
507 let webContents = views[id][i].view.webContents;
508 webContents.loadURL(config.get('homePage.defaultPage'));
513 ipcMain.on(`browserview-loadURL-${id}`, (e, args) => {
514 views[id].filter(function (view, i) {
515 if (view.id == args.id) {
516 let webContents = views[id][i].view.webContents;
517 webContents.loadURL(args.url);
522 ipcMain.on(`browserview-loadFile-${id}`, (e, args) => {
523 views[id].filter(function (view, i) {
524 if (view.id == args.id) {
525 let webContents = views[id][i].view.webContents;
526 webContents.loadFile(args.url);
531 ipcMain.on(`data-bookmark-add-${id}`, (e, args) => {
532 views[id].filter((view, i) => {
533 if (view.id == args.id) {
534 let v = views[id][i].view;
535 db.bookmarks.insert({ title: v.webContents.getTitle(), url: v.webContents.getURL(), isPrivate: args.isPrivate });
536 this.updateBookmarkState(id, args.id, v);
541 ipcMain.on(`data-bookmark-remove-${id}`, (e, args) => {
542 views[id].filter((view, i) => {
543 if (view.id == args.id) {
544 let v = views[id][i].view;
545 db.bookmarks.remove({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, {});
546 this.updateBookmarkState(id, args.id, v);
551 ipcMain.on(`data-bookmark-has-${id}`, (e, args) => {
552 views[id].filter((view, i) => {
553 if (view.id == args.id) {
554 let v = views[id][i].view;
555 db.bookmarks.find({ url: v.webContents.getURL(), isPrivate: args.isPrivate }, (err, docs) => {
556 e.sender.send(`data-bookmark-has-${id}`, { isBookmarked: (docs.length > 0 ? true : false) });
563 getFavicon = (url) => {
564 const parsed = parse(url);
565 return url.startsWith(`${protocolStr}://`) || url.startsWith(`${fileProtocolStr}://`) ? undefined : `https://www.google.com/s2/favicons?domain=${parsed.protocol}//${parsed.hostname}`;
568 updateNavigationState = (windowId, id, view) => {
569 const window = this.windows.get(windowId);
570 window.webContents.send(`update-navigation-state-${windowId}`, {
572 canGoBack: view.webContents.canGoBack(),
573 canGoForward: view.webContents.canGoForward(),
577 updateBookmarkState = (windowId, id, view) => {
578 const window = this.windows.get(windowId);
579 db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
580 const url = view.webContents.getURL();
581 window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), isBookmarked: (docs.length > 0 ? true : false) });
585 fixBounds = (windowId, isFloating = false) => {
586 const window = this.windows.get(windowId);
588 if (window.getBrowserView() == undefined) return;
589 const view = window.getBrowserView();
591 const { width, height } = window.getContentBounds();
593 view.setAutoResize({ width: true, height: true });
595 window.setMinimizable(false);
596 window.setMaximizable(false);
597 window.setAlwaysOnTop(true);
598 window.setVisibleOnAllWorkspaces(true);
606 window.setMinimizable(true);
607 window.setMaximizable(true);
608 window.setAlwaysOnTop(false);
609 window.setVisibleOnAllWorkspaces(false);
610 if (window.isFullScreen()) {
622 height: window.isMaximized() ? height - 73 : (height - 73) - 2,
626 view.setAutoResize({ width: true, height: true });
629 addView = (windowId, url, isActive) => {
630 if (String(windowId).startsWith('private')) {
631 loadSessionAndProtocolWithPrivateMode(windowId);
634 const id = tabCount++;
635 this.addTab(windowId, id, url, isActive);
638 removeView = (windowId, id) => {
639 views[windowId].filter((view, i) => {
643 if (index + 1 < views[windowId].length) {
644 this.selectView2(windowId, index + 1);
645 } else if (index - 1 >= 0) {
646 this.selectView2(windowId, index - 1);
649 views[windowId][index].view.destroy();
650 views[windowId].splice(index, 1);
655 selectView = (windowId, id) => {
656 const window = this.windows.get(windowId);
657 views[windowId].filter((view, i) => {
659 window.setBrowserView(views[windowId][i].view);
660 window.setTitle(views[windowId][i].view.webContents.getTitle());
661 window.webContents.send(`browserview-set-${windowId}`, { id: id });
662 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
667 selectView2 = (windowId, i) => {
668 const window = this.windows.get(windowId);
669 const item = views[windowId][i];
671 window.setBrowserView(item.view);
672 window.setTitle(item.view.webContents.getTitle());
673 window.webContents.send(`browserview-set-${windowId}`, { id: item.id });
674 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
677 getViews = (windowId) => {
679 for (var i = 0; i < views[windowId].length; i++) {
680 const url = views[windowId][i].view.webContents.getURL();
682 datas.push({ id: views[windowId][i].id, title: views[windowId][i].view.webContents.getTitle(), url: url, icon: this.getFavicon(url) });
684 const window = this.windows.get(windowId);
685 window.webContents.send(`browserview-get-${windowId}`, { views: datas });
688 addTab = (windowId, id, url = config.get('homePage.defaultPage'), isActive = true) => {
689 const window = this.windows.get(windowId);
691 const view = new BrowserView({
693 nodeIntegration: false,
694 contextIsolation: false,
696 experimentalFeatures: true,
698 safeDialogsMessage: '今後このページではダイアログを表示しない',
699 ...(String(windowId).startsWith('private') && { partition: windowId }),
700 preload: require.resolve('./Preload')
704 view.webContents.on('did-start-loading', () => {
705 if (view.isDestroyed()) return;
707 window.webContents.send(`browserview-start-loading-${windowId}`, { id: id });
709 view.webContents.on('did-stop-loading', () => {
710 if (view.isDestroyed()) return;
712 window.webContents.send(`browserview-stop-loading-${windowId}`, { id: id });
715 view.webContents.on('did-finish-load', (e) => {
716 if (view.isDestroyed()) return;
718 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
719 this.updateBookmarkState(windowId, id, view);
721 this.updateNavigationState(windowId, id, view);
724 view.webContents.on('did-start-navigation', (e) => {
725 if (view.isDestroyed()) return;
727 const url = view.webContents.getURL();
729 if (config.get('adBlocker') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
730 removeAds(url, view.webContents);
732 this.updateNavigationState(windowId, id, view);
735 view.webContents.on('page-title-updated', (e) => {
736 if (view.isDestroyed()) return;
738 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
739 this.updateBookmarkState(windowId, id, view);
741 if (!String(windowId).startsWith('private') && !(view.webContents.getURL().startsWith(`${protocolStr}://`) || view.webContents.getURL().startsWith(`${fileProtocolStr}://`)))
742 db.historys.insert({ title: view.webContents.getTitle(), url: view.webContents.getURL() });
744 this.updateNavigationState(windowId, id, view);
747 view.webContents.on('page-favicon-updated', (e, favicons) => {
748 if (view.isDestroyed()) return;
750 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
751 db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
752 const url = view.webContents.getURL();
753 window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: url, icon: this.getFavicon(url), isBookmarked: (docs.length > 0 ? true : false) });
756 this.updateNavigationState(windowId, id, view);
759 view.webContents.on('did-change-theme-color', (e, color) => {
760 if (view.isDestroyed()) return;
762 window.setTitle(`${view.webContents.getTitle()} - ${pkg.name}`);
763 db.bookmarks.find({ url: view.webContents.getURL(), isPrivate: (String(windowId).startsWith('private')) }, (err, docs) => {
764 window.webContents.send(`browserview-load-${windowId}`, { id: id, title: view.webContents.getTitle(), url: view.webContents.getURL(), color: color, isBookmarked: (docs.length > 0 ? true : false) });
768 view.webContents.on('new-window', (e, url) => {
769 if (view.isDestroyed()) return;
772 this.addView(windowId, url, true);
775 view.webContents.on('certificate-error', (e, url, error, certificate, callback) => {
778 const dlg = dialog.showMessageBox({
781 message: 'この接続ではプライバシーが保護されません',
782 detail: `${parse(url).hostname} の証明書を信頼することができませんでした。\n信頼できるページに戻ることをおすすめします。\nこのまま閲覧することも可能ですが安全ではありません。`,
784 buttons: ['続行', 'キャンセル'],
788 // e.preventDefault();
792 view.webContents.on('context-menu', (e, params) => {
793 if (view.isDestroyed()) return;
795 const menu = Menu.buildFromTemplate(
797 ...(params.linkURL !== '' ?
802 this.addView(windowId, params.linkURL, true);
806 label: '新しいウィンドウで開く',
808 click: () => { view.webContents.openDevTools(); }
811 label: 'プライベート ウィンドウで開く',
813 click: () => { view.webContents.openDevTools(); }
815 { type: 'separator' },
818 accelerator: 'CmdOrCtrl+C',
821 clipboard.writeText(params.linkURL);
824 { type: 'separator' }
826 ...(params.hasImageContents ?
829 label: '新しいタブで画像を開く',
831 this.addView(windowId, params.srcURL, true);
837 const img = nativeImage.createFromDataURL(params.srcURL);
840 clipboard.writeImage(img);
847 clipboard.writeText(params.srcURL);
850 { type: 'separator' }
852 ...(params.isEditable ?
856 accelerator: 'CmdOrCtrl+Z',
857 enabled: params.editFlags.canUndo,
858 click: () => { view.webContents.undo(); }
862 accelerator: 'CmdOrCtrl+Y',
863 enabled: params.editFlags.canRedo,
864 click: () => { view.webContents.redo(); }
866 { type: 'separator' },
869 accelerator: 'CmdOrCtrl+X',
870 enabled: params.editFlags.canCut,
871 click: () => { view.webContents.cut(); }
875 accelerator: 'CmdOrCtrl+C',
876 enabled: params.editFlags.canCopy,
877 click: () => { view.webContents.copy(); }
881 accelerator: 'CmdOrCtrl+V',
882 enabled: params.editFlags.canPaste,
883 click: () => { view.webContents.paste(); }
885 { type: 'separator' },
888 accelerator: 'CmdOrCtrl+A',
889 enabled: params.editFlags.canSelectAll,
890 click: () => { view.webContents.selectAll(); }
892 { type: 'separator' }
894 ...(params.selectionText !== '' && !params.isEditable ?
898 accelerator: 'CmdOrCtrl+C',
899 click: () => { view.webContents.copy(); }
902 label: `Googleで「${params.selectionText}」を検索`,
904 this.addView(windowId, `https://www.google.co.jp/search?q=${params.selectionText}`, true);
907 { type: 'separator' }
911 accelerator: 'Alt+Left',
912 icon: view.webContents.canGoBack() ? `${app.getAppPath()}/static/arrow_back.png` : `${app.getAppPath()}/static/arrow_back_inactive.png`,
913 enabled: view.webContents.canGoBack(),
914 click: () => { view.webContents.goBack(); }
918 accelerator: 'Alt+Right',
919 icon: view.webContents.canGoForward() ? `${app.getAppPath()}/static/arrow_forward.png` : `${app.getAppPath()}/static/arrow_forward_inactive.png`,
920 enabled: view.webContents.canGoForward(),
921 click: () => { view.webContents.goForward(); }
924 label: !view.webContents.isLoadingMainFrame() ? '再読み込み' : '読み込み中止',
925 accelerator: 'CmdOrCtrl+R',
926 icon: !view.webContents.isLoadingMainFrame() ? `${app.getAppPath()}/static/refresh.png` : `${app.getAppPath()}/static/close.png`,
927 click: () => { !view.webContents.isLoadingMainFrame() ? view.webContents.reload() : view.webContents.stop(); }
929 { type: 'separator' },
931 label: 'Floating Window (Beta)',
933 checked: (floatingWindows.indexOf(windowId) != -1),
934 enabled: (!window.isFullScreen() && !window.isMaximized() && config.get('window.isCustomTitlebar')),
936 if (floatingWindows.indexOf(windowId) != -1) {
937 floatingWindows.filter((win, i) => {
938 if (windowId == win) {
939 floatingWindows.splice(i, 1);
943 floatingWindows.push(windowId);
945 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
948 { type: 'separator' },
951 accelerator: 'CmdOrCtrl+S',
952 enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
954 view.webContents.savePage(`${app.getPath('downloads')}/${view.webContents.getTitle()}.html`, 'HTMLComplete', (err) => {
955 if (!err) console.log('Page Save successfully');
961 accelerator: 'CmdOrCtrl+P',
962 icon: `${app.getAppPath()}/static/print.png`,
963 enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
964 click: () => { view.webContents.print(); }
966 { type: 'separator' },
969 accelerator: 'CmdOrCtrl+Shift+I',
970 enabled: !view.webContents.getURL().startsWith(`${protocolStr}://`),
971 click: () => { if (view.webContents.isDevToolsOpened()) { view.webContents.devToolsWebContents.focus(); } else { view.webContents.openDevTools(); } }
979 view.webContents.on('before-input-event', (e, input) => {
980 if (view.isDestroyed()) return;
982 if (input.control || (platform.isDarwin && input.meta)) {
983 console.log(input.control, input.alt, input.shift, input.key);
985 if (input.shift && input.key == 'I') {
987 if (view.webContents.isDevToolsOpened()) {
988 view.webContents.devToolsWebContents.focus();
990 view.webContents.openDevTools();
992 } else if (input.shift && input.key == 'R') {
994 view.webContents.reloadIgnoringCache();
995 } else if (input.key == 'T') {
997 this.addView(windowId, config.get('homePage.defaultPage'), true)
999 } else if (input.alt) {
1000 if (input.key == 'ArrowLeft') {
1002 if (view.webContents.canGoBack())
1003 view.webContents.goBack();
1004 } else if (input.key == 'ArrowRight') {
1006 if (view.webContents.canGoForward())
1007 view.webContents.goForward();
1010 if (input.key == 'F11') {
1012 window.setFullScreen(!window.isFullScreen());
1013 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1018 view.webContents.session.on('will-download', (event, item, webContents) => {
1019 const str = this.getRandString(12);
1020 db.downloads.insert({ id: str, name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: item.getState() });
1021 item.on('updated', (e, state) => {
1022 db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
1025 item.once('done', (e, state) => {
1026 db.downloads.update({ id: str }, { $set: { name: item.getFilename(), url: item.getURL(), type: item.getMimeType(), size: item.getTotalBytes(), path: item.getSavePath(), status: state } });
1030 view.webContents.loadURL(url);
1032 if (views[windowId] == undefined)
1033 views[windowId] = [];
1034 views[windowId].push({ id, view, isNotificationBar: false });
1038 window.webContents.send(`browserview-set-${windowId}`, { id: id });
1039 window.setBrowserView(view);
1042 this.fixBounds(windowId, (floatingWindows.indexOf(windowId) != -1));
1043 this.getViews(windowId);
1046 getRandString = (length) => {
1047 const char = 'abcdefghijklmnopqrstuvwxyz0123456789';
1048 const charLength = char.length;
1051 for (var i = 0; i < length; i++) {
1052 str += char[Math.floor(Math.random() * charLength)];