aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Dominik Guzei <dominik.guzei@gmail.com>2019-03-08 17:34:27 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-03-08 08:34:27 -0800
commitbaa45d935540601c07977e4d786e0953165cf1fa (patch)
treeeee0423829cd4b8fb6306620edd8e85bc6fdaed8
parentfix(Linux): Fix minimized window focusing (#1304) (@skoruppa) (diff)
downloadferdium-app-baa45d935540601c07977e4d786e0953165cf1fa.tar.gz
ferdium-app-baa45d935540601c07977e4d786e0953165cf1fa.tar.zst
ferdium-app-baa45d935540601c07977e4d786e0953165cf1fa.zip
Fix/service webview unmounting (#1328)
* detach service when underlying webview unmounts * disable no-param-reassign eslint rule
-rw-r--r--.eslintrc1
-rw-r--r--src/actions/service.js4
-rw-r--r--src/components/services/content/ServiceView.js136
-rw-r--r--src/components/services/content/ServiceWebview.js145
-rw-r--r--src/components/services/content/Services.js7
-rw-r--r--src/containers/layout/AppLayoutContainer.js3
-rw-r--r--src/stores/ServicesStore.js6
7 files changed, 180 insertions, 122 deletions
diff --git a/.eslintrc b/.eslintrc
index e15148e96..743946d35 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,6 +2,7 @@
2 "parser": "babel-eslint", 2 "parser": "babel-eslint",
3 "extends": "eslint-config-airbnb", 3 "extends": "eslint-config-airbnb",
4 "rules": { 4 "rules": {
5 "no-param-reassign": 0,
5 "import/extensions": 0, 6 "import/extensions": 0,
6 "import/no-extraneous-dependencies": 0, 7 "import/no-extraneous-dependencies": 0,
7 "import/no-unresolved": [2, { 8 "import/no-unresolved": [2, {
diff --git a/src/actions/service.js b/src/actions/service.js
index 5d483b12a..ceaabc31e 100644
--- a/src/actions/service.js
+++ b/src/actions/service.js
@@ -1,4 +1,5 @@
1import PropTypes from 'prop-types'; 1import PropTypes from 'prop-types';
2import ServiceModel from '../models/Service';
2 3
3export default { 4export default {
4 setActive: { 5 setActive: {
@@ -36,6 +37,9 @@ export default {
36 serviceId: PropTypes.string.isRequired, 37 serviceId: PropTypes.string.isRequired,
37 webview: PropTypes.object.isRequired, 38 webview: PropTypes.object.isRequired,
38 }, 39 },
40 detachService: {
41 service: PropTypes.instanceOf(ServiceModel).isRequired,
42 },
39 focusService: { 43 focusService: {
40 serviceId: PropTypes.string.isRequired, 44 serviceId: PropTypes.string.isRequired,
41 }, 45 },
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
new file mode 100644
index 000000000..5afc54f9d
--- /dev/null
+++ b/src/components/services/content/ServiceView.js
@@ -0,0 +1,136 @@
1import React, { Component, Fragment } from 'react';
2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { observer } from 'mobx-react';
5import classnames from 'classnames';
6
7import ServiceModel from '../../../models/Service';
8import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
9import WebviewLoader from '../../ui/WebviewLoader';
10import WebviewCrashHandler from './WebviewCrashHandler';
11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
12import ServiceDisabled from './ServiceDisabled';
13import ServiceWebview from './ServiceWebview';
14
15export default @observer class ServiceView extends Component {
16 static propTypes = {
17 service: PropTypes.instanceOf(ServiceModel).isRequired,
18 setWebviewReference: PropTypes.func.isRequired,
19 detachService: PropTypes.func.isRequired,
20 reload: PropTypes.func.isRequired,
21 edit: PropTypes.func.isRequired,
22 enable: PropTypes.func.isRequired,
23 isActive: PropTypes.bool,
24 };
25
26 static defaultProps = {
27 isActive: false,
28 };
29
30 state = {
31 forceRepaint: false,
32 targetUrl: '',
33 statusBarVisible: false,
34 };
35
36 autorunDisposer = null;
37
38 componentDidMount() {
39 this.autorunDisposer = autorun(() => {
40 if (this.props.service.isActive) {
41 this.setState({ forceRepaint: true });
42 setTimeout(() => {
43 this.setState({ forceRepaint: false });
44 }, 100);
45 }
46 });
47 }
48
49 componentWillUnmount() {
50 this.autorunDisposer();
51 }
52
53 updateTargetUrl = (event) => {
54 let visible = true;
55 if (event.url === '' || event.url === '#') {
56 visible = false;
57 }
58 this.setState({
59 targetUrl: event.url,
60 statusBarVisible: visible,
61 });
62 };
63
64 render() {
65 const {
66 detachService,
67 service,
68 setWebviewReference,
69 reload,
70 edit,
71 enable,
72 } = this.props;
73
74 const webviewClasses = classnames({
75 services__webview: true,
76 'services__webview-wrapper': true,
77 'is-active': service.isActive,
78 'services__webview--force-repaint': this.state.forceRepaint,
79 });
80
81 let statusBar = null;
82 if (this.state.statusBarVisible) {
83 statusBar = (
84 <StatusBarTargetUrl text={this.state.targetUrl} />
85 );
86 }
87
88 return (
89 <div className={webviewClasses}>
90 {service.isActive && service.isEnabled && (
91 <Fragment>
92 {service.hasCrashed && (
93 <WebviewCrashHandler
94 name={service.recipe.name}
95 webview={service.webview}
96 reload={reload}
97 />
98 )}
99 {service.isEnabled && service.isLoading && service.isFirstLoad && (
100 <WebviewLoader
101 loaded={false}
102 name={service.name}
103 />
104 )}
105 {service.isError && (
106 <WebviewErrorHandler
107 name={service.recipe.name}
108 errorMessage={service.errorMessage}
109 reload={reload}
110 edit={edit}
111 />
112 )}
113 </Fragment>
114 )}
115 {!service.isEnabled ? (
116 <Fragment>
117 {service.isActive && (
118 <ServiceDisabled
119 name={service.recipe.name}
120 webview={service.webview}
121 enable={enable}
122 />
123 )}
124 </Fragment>
125 ) : (
126 <ServiceWebview
127 service={service}
128 setWebviewReference={setWebviewReference}
129 detachService={detachService}
130 />
131 )}
132 {statusBar}
133 </div>
134 );
135 }
136}
diff --git a/src/components/services/content/ServiceWebview.js b/src/components/services/content/ServiceWebview.js
index bb577e4cc..7252c695f 100644
--- a/src/components/services/content/ServiceWebview.js
+++ b/src/components/services/content/ServiceWebview.js
@@ -1,145 +1,50 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { autorun } from 'mobx';
4import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
5import Webview from 'react-electron-web-view'; 4import ElectronWebView from 'react-electron-web-view';
6import classnames from 'classnames';
7 5
8import ServiceModel from '../../../models/Service'; 6import ServiceModel from '../../../models/Service';
9import StatusBarTargetUrl from '../../ui/StatusBarTargetUrl';
10import WebviewLoader from '../../ui/WebviewLoader';
11import WebviewCrashHandler from './WebviewCrashHandler';
12import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
13import ServiceDisabled from './ServiceDisabled';
14 7
15export default @observer class ServiceWebview extends Component { 8@observer
9class ServiceWebview extends Component {
16 static propTypes = { 10 static propTypes = {
17 service: PropTypes.instanceOf(ServiceModel).isRequired, 11 service: PropTypes.instanceOf(ServiceModel).isRequired,
18 setWebviewReference: PropTypes.func.isRequired, 12 setWebviewReference: PropTypes.func.isRequired,
19 reload: PropTypes.func.isRequired, 13 detachService: PropTypes.func.isRequired,
20 edit: PropTypes.func.isRequired,
21 enable: PropTypes.func.isRequired,
22 isActive: PropTypes.bool,
23 }; 14 };
24 15
25 static defaultProps = {
26 isActive: false,
27 };
28
29 state = {
30 forceRepaint: false,
31 targetUrl: '',
32 statusBarVisible: false,
33 };
34
35 autorunDisposer = null;
36
37 webview = null; 16 webview = null;
38 17
39 componentDidMount() {
40 this.autorunDisposer = autorun(() => {
41 if (this.props.service.isActive) {
42 this.setState({ forceRepaint: true });
43 setTimeout(() => {
44 this.setState({ forceRepaint: false });
45 }, 100);
46 }
47 });
48 }
49
50 componentWillUnmount() { 18 componentWillUnmount() {
51 this.autorunDisposer(); 19 const { service, detachService } = this.props;
52 } 20 detachService({ service });
53
54 updateTargetUrl = (event) => {
55 let visible = true;
56 if (event.url === '' || event.url === '#') {
57 visible = false;
58 }
59 this.setState({
60 targetUrl: event.url,
61 statusBarVisible: visible,
62 });
63 } 21 }
64 22
65 render() { 23 render() {
66 const { 24 const {
67 service, 25 service,
68 setWebviewReference, 26 setWebviewReference,
69 reload,
70 edit,
71 enable,
72 } = this.props; 27 } = this.props;
73 28
74 const webviewClasses = classnames({
75 services__webview: true,
76 'services__webview-wrapper': true,
77 'is-active': service.isActive,
78 'services__webview--force-repaint': this.state.forceRepaint,
79 });
80
81 let statusBar = null;
82 if (this.state.statusBarVisible) {
83 statusBar = (
84 <StatusBarTargetUrl text={this.state.targetUrl} />
85 );
86 }
87
88 return ( 29 return (
89 <div className={webviewClasses}> 30 <ElectronWebView
90 {service.isActive && service.isEnabled && ( 31 ref={(webview) => { this.webview = webview; }}
91 <Fragment> 32 autosize
92 {service.hasCrashed && ( 33 src={service.url}
93 <WebviewCrashHandler 34 preload="./webview/recipe.js"
94 name={service.recipe.name} 35 partition={`persist:service-${service.id}`}
95 webview={service.webview} 36 onDidAttach={() => {
96 reload={reload} 37 setWebviewReference({
97 /> 38 serviceId: service.id,
98 )} 39 webview: this.webview.view,
99 {service.isEnabled && service.isLoading && service.isFirstLoad && ( 40 });
100 <WebviewLoader 41 }}
101 loaded={false} 42 onUpdateTargetUrl={this.updateTargetUrl}
102 name={service.name} 43 useragent={service.userAgent}
103 /> 44 allowpopups
104 )} 45 />
105 {service.isError && (
106 <WebviewErrorHandler
107 name={service.recipe.name}
108 errorMessage={service.errorMessage}
109 reload={reload}
110 edit={edit}
111 />
112 )}
113 </Fragment>
114 )}
115 {!service.isEnabled ? (
116 <Fragment>
117 {service.isActive && (
118 <ServiceDisabled
119 name={service.recipe.name}
120 webview={service.webview}
121 enable={enable}
122 />
123 )}
124 </Fragment>
125 ) : (
126 <Webview
127 ref={(element) => { this.webview = element; }}
128 autosize
129 src={service.url}
130 preload="./webview/recipe.js"
131 partition={`persist:service-${service.id}`}
132 onDidAttach={() => setWebviewReference({
133 serviceId: service.id,
134 webview: this.webview.view,
135 })}
136 onUpdateTargetUrl={this.updateTargetUrl}
137 useragent={service.userAgent}
138 allowpopups
139 />
140 )}
141 {statusBar}
142 </div>
143 ); 46 );
144 } 47 }
145} 48}
49
50export default ServiceWebview;
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 54f16ba12..8f8c38a11 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -4,7 +4,7 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, intlShape } from 'react-intl';
6 6
7import Webview from './ServiceWebview'; 7import ServiceView from './ServiceView';
8import Appear from '../../ui/effects/Appear'; 8import Appear from '../../ui/effects/Appear';
9 9
10const messages = defineMessages({ 10const messages = defineMessages({
@@ -22,6 +22,7 @@ export default @observer class Services extends Component {
22 static propTypes = { 22 static propTypes = {
23 services: MobxPropTypes.arrayOrObservableArray, 23 services: MobxPropTypes.arrayOrObservableArray,
24 setWebviewReference: PropTypes.func.isRequired, 24 setWebviewReference: PropTypes.func.isRequired,
25 detachService: PropTypes.func.isRequired,
25 handleIPCMessage: PropTypes.func.isRequired, 26 handleIPCMessage: PropTypes.func.isRequired,
26 openWindow: PropTypes.func.isRequired, 27 openWindow: PropTypes.func.isRequired,
27 reload: PropTypes.func.isRequired, 28 reload: PropTypes.func.isRequired,
@@ -42,6 +43,7 @@ export default @observer class Services extends Component {
42 services, 43 services,
43 handleIPCMessage, 44 handleIPCMessage,
44 setWebviewReference, 45 setWebviewReference,
46 detachService,
45 openWindow, 47 openWindow,
46 reload, 48 reload,
47 openSettings, 49 openSettings,
@@ -71,11 +73,12 @@ export default @observer class Services extends Component {
71 </Appear> 73 </Appear>
72 )} 74 )}
73 {services.map(service => ( 75 {services.map(service => (
74 <Webview 76 <ServiceView
75 key={service.id} 77 key={service.id}
76 service={service} 78 service={service}
77 handleIPCMessage={handleIPCMessage} 79 handleIPCMessage={handleIPCMessage}
78 setWebviewReference={setWebviewReference} 80 setWebviewReference={setWebviewReference}
81 detachService={detachService}
79 openWindow={openWindow} 82 openWindow={openWindow}
80 reload={() => reload({ serviceId: service.id })} 83 reload={() => reload({ serviceId: service.id })}
81 edit={() => openSettings({ path: `services/edit/${service.id}` })} 84 edit={() => openSettings({ path: `services/edit/${service.id}` })}
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index 749912c59..5a05ce431 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -42,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
42 setActive, 42 setActive,
43 handleIPCMessage, 43 handleIPCMessage,
44 setWebviewReference, 44 setWebviewReference,
45 detachService,
45 openWindow, 46 openWindow,
46 reorder, 47 reorder,
47 reload, 48 reload,
@@ -105,6 +106,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
105 services={services.allDisplayedUnordered} 106 services={services.allDisplayedUnordered}
106 handleIPCMessage={handleIPCMessage} 107 handleIPCMessage={handleIPCMessage}
107 setWebviewReference={setWebviewReference} 108 setWebviewReference={setWebviewReference}
109 detachService={detachService}
108 openWindow={openWindow} 110 openWindow={openWindow}
109 reload={reload} 111 reload={reload}
110 openSettings={openSettings} 112 openSettings={openSettings}
@@ -160,6 +162,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
160 toggleAudio: PropTypes.func.isRequired, 162 toggleAudio: PropTypes.func.isRequired,
161 handleIPCMessage: PropTypes.func.isRequired, 163 handleIPCMessage: PropTypes.func.isRequired,
162 setWebviewReference: PropTypes.func.isRequired, 164 setWebviewReference: PropTypes.func.isRequired,
165 detachService: PropTypes.func.isRequired,
163 openWindow: PropTypes.func.isRequired, 166 openWindow: PropTypes.func.isRequired,
164 reloadUpdatedServices: PropTypes.func.isRequired, 167 reloadUpdatedServices: PropTypes.func.isRequired,
165 updateService: PropTypes.func.isRequired, 168 updateService: PropTypes.func.isRequired,
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index c63bef196..69e616f0c 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -44,6 +44,7 @@ export default class ServicesStore extends Store {
44 this.actions.service.deleteService.listen(this._deleteService.bind(this)); 44 this.actions.service.deleteService.listen(this._deleteService.bind(this));
45 this.actions.service.clearCache.listen(this._clearCache.bind(this)); 45 this.actions.service.clearCache.listen(this._clearCache.bind(this));
46 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this)); 46 this.actions.service.setWebviewReference.listen(this._setWebviewReference.bind(this));
47 this.actions.service.detachService.listen(this._detachService.bind(this));
47 this.actions.service.focusService.listen(this._focusService.bind(this)); 48 this.actions.service.focusService.listen(this._focusService.bind(this));
48 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this)); 49 this.actions.service.focusActiveService.listen(this._focusActiveService.bind(this));
49 this.actions.service.toggleService.listen(this._toggleService.bind(this)); 50 this.actions.service.toggleService.listen(this._toggleService.bind(this));
@@ -341,6 +342,11 @@ export default class ServicesStore extends Store {
341 service.isAttached = true; 342 service.isAttached = true;
342 } 343 }
343 344
345 @action _detachService({ service }) {
346 service.webview = null;
347 service.isAttached = false;
348 }
349
344 @action _focusService({ serviceId }) { 350 @action _focusService({ serviceId }) {
345 const service = this.one(serviceId); 351 const service = this.one(serviceId);
346 352