From f8e7d64b085896a909494264df0580179c53dc77 Mon Sep 17 00:00:00 2001 From: rhysd Date: Mon, 24 Apr 2017 18:31:55 +0900 Subject: [PATCH] load preload plugins after app gets idling --- README.md | 4 +- renderer/index.ts | 7 ++- renderer/plugins.ts | 82 ++++++++++++++++++++++++------ typings/request-idle-callback.d.ts | 9 ++++ 4 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 typings/request-idle-callback.d.ts diff --git a/README.md b/README.md index db9422b..e96320f 100644 --- a/README.md +++ b/README.md @@ -264,14 +264,14 @@ body { } ``` -## Preload Plugin +## Preload Plugin (experimental) You can make a Node.js package which is preloaded before loading inner mastodon page. Preload plugin is enabled if `chromium_sandbox` option is set to `false`. Please read above configuration section before using any plugin. -### How to make +### How to make a plugin Create `node_modules` directory in your application directory at first. And then, please make `mstdn-preload-hello` directory. It consists a node package. diff --git a/renderer/index.ts b/renderer/index.ts index 9b2d649..611aa72 100644 --- a/renderer/index.ts +++ b/renderer/index.ts @@ -1,12 +1,11 @@ import {Config, Account} from '../main/config'; import * as Ipc from './ipc'; import setupKeymaps from './key_handler'; -import loadPlugins from './plugins'; -import log from './log'; +import PluginsLoader from './plugins'; Ipc.on('mstdn:config', (c: Config, a: Account) => { - const plugins = loadPlugins(c, a); - log.info('Loaded plugins:', plugins); + const loader = new PluginsLoader(c, a); + loader.loadAfterAppPrepared(); setupKeymaps(c, a); document.title = `${document.title} @${a.name}@${a.host}`; }); diff --git a/renderer/plugins.ts b/renderer/plugins.ts index 8ff899e..3587210 100644 --- a/renderer/plugins.ts +++ b/renderer/plugins.ts @@ -8,22 +8,72 @@ interface Plugins { [module_path: string]: Plugin; } -export default function loadPlugins(config: Config, account: Account): Plugins { - const ret = {} as Plugins; - if (config.chromium_sandbox) { - log.info('Chromium sandbox is enabled. Preload plugin is disabled.'); - return ret; - } - const dir_base = path.join(config.__DATA_DIR!, 'node_modules'); - for (const plugin of config.preload || []) { - const plugin_path = path.join(dir_base, `mstdn-preload-${plugin}`); - try { - const preloadFunc = r(plugin_path) as Plugin; - preloadFunc(config, account); - ret[plugin_path] = preloadFunc; - } catch (e) { - log.error(`Failed to load plugin ${plugin_path}:`, e); +export default class PluginsLoader { + preloads: Plugins; + loaded: boolean; + + constructor(private config: Config, private account: Account) { + this.loaded = false; + this.preloads = {}; + + if (config.chromium_sandbox) { + log.info('Chromium sandbox is enabled. Preload plugin is disabled.'); + return; + } + + const dir_base = path.join(config.__DATA_DIR!, 'node_modules'); + for (const plugin of config.preload || []) { + const plugin_path = path.join(dir_base, `mstdn-preload-${plugin}`); + try { + this.preloads[plugin_path] = r(plugin_path) as Plugin; + } catch (e) { + log.error(`Failed to load plugin ${plugin_path}:`, e); + } } } - return ret; + + loadAfterAppPrepared() { + if (Object.keys(this.preloads).length === 0) { + log.info('No preload plugin found. Skip loading'); + this.loaded = true; + return; + } + + return new Promise(resolve => { + // In order not to prevent application's initial loading, load preload plugins + // on an idle callback. + window.requestIdleCallback(() => { + log.debug('Start loading preload plugins', this.config, this.account); + if (this.tryLoading()) { + return resolve(); + } + this.observeAppPrepared(this.tryLoading).then(resolve); + }); + }); + } + + observeAppPrepared(callback: () => void) { + // TODO: + // Make an instance of MutationObserver to observe React root. + return Promise.resolve(callback()); + } + + tryLoading = () => { + if (document.querySelector('[data-react-class="Mastodon"]') === null) { + log.info('Root element of react app was not found. App seems not to be loaded yet.'); + return false; + } + + for (const key in this.preloads) { + const f = this.preloads[key]; + try { + f(this.config, this.account); + } catch (e) { + log.error(`Error while loading preload plugin '${key}':`, e); + } + } + + log.info('Preload plugins were loaded:', this.preloads); + return true; + } } diff --git a/typings/request-idle-callback.d.ts b/typings/request-idle-callback.d.ts new file mode 100644 index 0000000..7d4324e --- /dev/null +++ b/typings/request-idle-callback.d.ts @@ -0,0 +1,9 @@ +interface RequestIdleCallback { + didTimeout?: boolean; + timeRemaining?: () => number; +} + +interface Window { + requestIdleCallback(cb: (deadline: RequestIdleCallback) => any): NodeJS.Timer; + cancelIdleCallback(id: NodeJS.Timer): void; +}