'use strict'; const path = require('path'); const fs = require('fs'); const ConfigUtil = require(__dirname + '/../utils/config-util.js'); const SystemUtil = require(__dirname + '/../utils/system-util.js'); const { app, dialog } = require('electron').remote; const BaseComponent = require(__dirname + '/../components/base.js'); const handleExternalLink = require(__dirname + '/../components/handle-external-link.js'); const shouldSilentWebview = ConfigUtil.getConfigItem('silent'); class WebView extends BaseComponent { constructor(props) { super(); this.props = props; this.zoomFactor = 1.0; this.loading = true; this.badgeCount = 0; this.customCSS = ConfigUtil.getConfigItem('customCSS'); this.$webviewsContainer = document.querySelector('#webviews-container').classList; } template() { return ` `; } init() { this.$el = this.generateNodeFromTemplate(this.template()); this.props.$root.appendChild(this.$el); this.registerListeners(); } registerListeners() { this.$el.addEventListener('new-window', event => { handleExternalLink.call(this, event); }); if (shouldSilentWebview) { this.$el.addEventListener('dom-ready', () => { this.$el.setAudioMuted(true); }); } this.$el.addEventListener('page-title-updated', event => { const { title } = event; this.badgeCount = this.getBadgeCount(title); this.props.onTitleChange(); }); this.$el.addEventListener('did-navigate-in-page', event => { const isSettingPage = event.url.includes('renderer/preference.html'); if (isSettingPage) { return; } this.canGoBackButton(); }); this.$el.addEventListener('did-navigate', () => { this.canGoBackButton(); }); this.$el.addEventListener('page-favicon-updated', event => { const { favicons } = event; // This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like // https://chat.zulip.org/static/images/favicon/favicon-pms.png if (favicons[0].indexOf('favicon-pms') > 0 && process.platform === 'darwin') { // This api is only supported on macOS app.dock.setBadge('●'); // bounce the dock if (ConfigUtil.getConfigItem('dockBouncing')) { app.dock.bounce(); } } }); this.$el.addEventListener('dom-ready', () => { if (this.props.role === 'server') { this.$el.classList.add('onload'); } this.loading = false; this.props.switchLoading(false, this.props.url); this.show(); // Refocus text boxes after reload // Remove when upstream issue https://github.com/electron/electron/issues/14474 is fixed this.$el.blur(); this.$el.focus(); }); this.$el.addEventListener('did-fail-load', event => { const { errorDescription } = event; const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0); if (hasConnectivityErr) { console.error('error', errorDescription); this.props.onNetworkError(); } }); this.$el.addEventListener('did-start-loading', () => { const isSettingPage = this.props.url.includes('renderer/preference.html'); if (!isSettingPage) { this.props.switchLoading(true, this.props.url); } let userAgent = SystemUtil.getUserAgent(); if (!userAgent) { SystemUtil.setUserAgent(this.$el.getUserAgent()); userAgent = SystemUtil.getUserAgent(); } this.$el.setUserAgent(userAgent); }); this.$el.addEventListener('did-stop-loading', () => { this.props.switchLoading(false, this.props.url); }); } getBadgeCount(title) { const messageCountInTitle = (/\(([0-9]+)\)/).exec(title); return messageCountInTitle ? Number(messageCountInTitle[1]) : 0; } show() { // Do not show WebView if another tab was selected and this tab should be in background. if (!this.props.isActive()) { return; } // To show or hide the loading indicator in the the active tab if (this.loading) { this.$webviewsContainer.remove('loaded'); } else { this.$webviewsContainer.add('loaded'); } this.$el.classList.remove('disabled'); this.$el.classList.add('active'); setTimeout(() => { if (this.props.role === 'server') { this.$el.classList.remove('onload'); } }, 1000); this.focus(); this.props.onTitleChange(); // Injecting preload css in webview to override some css rules this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8')); // get customCSS again from config util to avoid warning user again this.customCSS = ConfigUtil.getConfigItem('customCSS'); if (this.customCSS) { if (!fs.existsSync(this.customCSS)) { this.customCSS = null; ConfigUtil.setConfigItem('customCSS', null); const errMsg = 'The custom css previously set is deleted!'; dialog.showErrorBox('custom css file deleted!', errMsg); return; } this.$el.insertCSS(fs.readFileSync(path.resolve(__dirname, this.customCSS), 'utf8')); } } focus() { // focus Webview and it's contents when Window regain focus. const webContents = this.$el.getWebContents(); // HACK: webContents.isFocused() seems to be true even without the element // being in focus. So, we check against `document.activeElement`. if (webContents && this.$el !== document.activeElement) { // HACK: Looks like blur needs to be called on the previously focused // element to transfer focus correctly, in Electron v3.0.10 // See https://github.com/electron/electron/issues/15718 document.activeElement.blur(); this.$el.focus(); webContents.focus(); } } hide() { this.$el.classList.add('disabled'); this.$el.classList.remove('active'); } load() { if (this.$el) { this.show(); } else { this.init(); } } zoomIn() { this.zoomFactor += 0.1; this.$el.setZoomFactor(this.zoomFactor); } zoomOut() { this.zoomFactor -= 0.1; this.$el.setZoomFactor(this.zoomFactor); } zoomActualSize() { this.zoomFactor = 1.0; this.$el.setZoomFactor(this.zoomFactor); } logOut() { this.$el.executeJavaScript('logout()'); } showShortcut() { this.$el.executeJavaScript('shortcut()'); } openDevTools() { this.$el.openDevTools(); } back() { if (this.$el.canGoBack()) { this.$el.goBack(); this.focus(); } } canGoBackButton() { const $backButton = document.querySelector('#actions-container #back-action'); if (this.$el.canGoBack()) { $backButton.classList.remove('disable'); } else { $backButton.classList.add('disable'); } } forward() { if (this.$el.canGoForward()) { this.$el.goForward(); } } reload() { this.hide(); // Shows the loading indicator till the webview is reloaded this.$webviewsContainer.remove('loaded'); this.loading = true; this.$el.reload(); } send(...param) { this.$el.send(...param); } } module.exports = WebView;