From 4f5f6f8e1204a7e66ab9f05e8313b272472fb459 Mon Sep 17 00:00:00 2001 From: windingwind <33902321+windingwind@users.noreply.github.com> Date: Mon, 24 Jul 2023 19:28:58 +0800 Subject: [PATCH] add: window load hook --- addon/bootstrap.js | 103 ++++++++++++++++++++++++++---------------- src/addon.ts | 44 ++---------------- src/hooks.ts | 29 ++++++++---- src/index.ts | 24 +++++----- src/utils/ztoolkit.ts | 48 ++++++++++++++++++++ typings/global.d.ts | 9 ++-- 6 files changed, 154 insertions(+), 103 deletions(-) create mode 100644 src/utils/ztoolkit.ts diff --git a/addon/bootstrap.js b/addon/bootstrap.js index 887e3ab..d70a135 100644 --- a/addon/bootstrap.js +++ b/addon/bootstrap.js @@ -11,6 +11,8 @@ if (typeof Zotero == "undefined") { var chromeHandle; +var windowListener; + // In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js // to get the Zotero XPCOM service would risk breaking Zotero startup. Instead, wait for the main // Zotero window to open and get the Zotero object from there. @@ -18,53 +20,72 @@ var chromeHandle; // In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is // automatically made available. async function waitForZotero() { - if (typeof Zotero != "undefined") { - await Zotero.initializationPromise; - } - - var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); - var windows = Services.wm.getEnumerator("navigator:browser"); - var found = false; - while (windows.hasMoreElements()) { - let win = windows.getNext(); - if (win.Zotero) { - Zotero = win.Zotero; - found = true; - break; + await new Promise(async (resolve) => { + if (typeof Zotero != "undefined") { + resolve(); } - } - if (!found) { - await new Promise((resolve) => { - var listener = { - onOpenWindow: function (aWindow) { - // Wait for the window to finish loading - let domWindow = aWindow - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); - domWindow.addEventListener( - "load", - function () { - domWindow.removeEventListener("load", arguments.callee, false); - if (domWindow.Zotero) { - Services.wm.removeListener(listener); - Zotero = domWindow.Zotero; - resolve(); - } - }, - false, - ); - }, - }; - Services.wm.addListener(listener); - }); - } - await Zotero.initializationPromise; + + const { Services } = ChromeUtils.import( + "resource://gre/modules/Services.jsm", + ); + const windows = Services.wm.getEnumerator("navigator:browser"); + let found = false; + while (windows.hasMoreElements()) { + let win = windows.getNext(); + if (win.Zotero) { + Zotero = win.Zotero; + found = true; + resolve(); + break; + } + } + windowListener = { + onOpenWindow: function (aWindow) { + // Wait for the window to finish loading + const domWindow = aWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); + domWindow.addEventListener( + "load", + async function () { + domWindow.removeEventListener("load", arguments.callee, false); + if (!found && domWindow.Zotero) { + Zotero = domWindow.Zotero; + resolve(); + } else if ( + domWindow.location.href === + "chrome://zotero/content/zoteroPane.xhtml" + ) { + // Call the hook for the main window load event + // Note that this is not called the first time the window is opened + // (when Zotero is initialized), but only when the window is re-opened + // after being closed + await Zotero.__addonInstance__?.hooks.onMainWindowLoad(domWindow); + } + }, + false, + ); + }, + onCloseWindow: function (aWindow) { + const domWindow = aWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); + if ( + domWindow.location.href === "chrome://zotero/content/zoteroPane.xhtml" + ) { + Zotero.__addonInstance__?.hooks.onMainWindowUnload(domWindow); + } + }, + }; + Services.wm.addListener(windowListener); + }); } function install(data, reason) {} async function startup({ id, version, resourceURI, rootURI }, reason) { await waitForZotero(); + await Zotero.initializationPromise; // String 'rootURI' introduced in Zotero 7 if (!rootURI) { @@ -100,6 +121,8 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) { if (reason === APP_SHUTDOWN) { return; } + Services.wm.removeListener(windowListener); + if (typeof Zotero === "undefined") { Zotero = Components.classes["@zotero.org/Zotero;1"].getService( Components.interfaces.nsISupports, diff --git a/src/addon.ts b/src/addon.ts index 7cf752d..a9a70af 100644 --- a/src/addon.ts +++ b/src/addon.ts @@ -1,14 +1,14 @@ -import ZoteroToolkit from "zotero-plugin-toolkit/dist/index"; import { ColumnOptions } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable"; +import { DialogHelper } from "zotero-plugin-toolkit/dist/helpers/dialog"; import hooks from "./hooks"; +import { createZToolkit } from "./utils/ztoolkit"; class Addon { public data: { alive: boolean; // Env type, see build.js env: "development" | "production"; - // ztoolkit: MyToolkit; - ztoolkit: ZoteroToolkit; + ztoolkit: ZToolkit; locale?: { current: any; }; @@ -28,47 +28,11 @@ class Addon { this.data = { alive: true, env: __env__, - // ztoolkit: new MyToolkit(), - ztoolkit: new ZoteroToolkit(), + ztoolkit: createZToolkit(), }; this.hooks = hooks; this.api = {}; } } -/** - * Alternatively, import toolkit modules you use to minify the plugin size. - * - * Steps to replace the default `ztoolkit: ZoteroToolkit` with your `ztoolkit: MyToolkit`: - * - * 1. Uncomment this file's line 30: `ztoolkit: new MyToolkit(),` - * and comment line 31: `ztoolkit: new ZoteroToolkit(),`. - * 2. Uncomment this file's line 10: `ztoolkit: MyToolkit;` in this file - * and comment line 11: `ztoolkit: ZoteroToolkit;`. - * 3. Uncomment `../typings/global.d.ts` line 12: `declare const ztoolkit: import("../src/addon").MyToolkit;` - * and comment line 13: `declare const ztoolkit: import("zotero-plugin-toolkit").ZoteroToolkit;`. - * - * You can now add the modules under the `MyToolkit` class. - */ - -import { BasicTool, unregister } from "zotero-plugin-toolkit/dist/basic"; -import { UITool } from "zotero-plugin-toolkit/dist/tools/ui"; -import { PreferencePaneManager } from "zotero-plugin-toolkit/dist/managers/preferencePane"; -import { DialogHelper } from "zotero-plugin-toolkit/dist/helpers/dialog"; - -export class MyToolkit extends BasicTool { - UI: UITool; - PreferencePane: PreferencePaneManager; - - constructor() { - super(); - this.UI = new UITool(this); - this.PreferencePane = new PreferencePaneManager(this); - } - - unregisterAll() { - unregister(this); - } -} - export default Addon; diff --git a/src/hooks.ts b/src/hooks.ts index 82e793c..7bf899a 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -8,6 +8,7 @@ import { import { config } from "../package.json"; import { getString, initLocale } from "./utils/locale"; import { registerPrefsScripts } from "./modules/preferenceScript"; +import { createZToolkit } from "./utils/ztoolkit"; async function onStartup() { await Promise.all([ @@ -16,10 +17,19 @@ async function onStartup() { Zotero.uiReadyPromise, ]); initLocale(); - ztoolkit.ProgressWindow.setIconURI( - "default", - `chrome://${config.addonRef}/content/icons/favicon.png`, - ); + + BasicExampleFactory.registerPrefs(); + + BasicExampleFactory.registerNotifier(); + + await onMainWindowLoad(window); +} + +async function onMainWindowLoad(win: Window): Promise { + // Create ztoolkit for every window + const _ztoolkit = createZToolkit(); + addon.data.ztoolkit = _ztoolkit; + _globalThis.ztoolkit = _ztoolkit; const popupWin = new ztoolkit.ProgressWindow(config.addonName, { closeOnClick: true, @@ -32,10 +42,6 @@ async function onStartup() { }) .show(); - BasicExampleFactory.registerPrefs(); - - BasicExampleFactory.registerNotifier(); - KeyExampleFactory.registerShortcuts(); await Zotero.Promise.delay(1000); @@ -81,6 +87,11 @@ async function onStartup() { addon.hooks.onDialogEvents("dialogExample"); } +async function onMainWindowUnload(win: Window): Promise { + ztoolkit.unregisterAll(); + addon.data.dialog?.window?.close(); +} + function onShutdown(): void { ztoolkit.unregisterAll(); addon.data.dialog?.window?.close(); @@ -173,6 +184,8 @@ function onDialogEvents(type: string) { export default { onStartup, onShutdown, + onMainWindowLoad, + onMainWindowUnload, onNotify, onPrefsEvent, onShortcuts, diff --git a/src/index.ts b/src/index.ts index 5326e2b..ae7d370 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,21 +7,21 @@ const basicTool = new BasicTool(); if (!basicTool.getGlobal("Zotero")[config.addonInstance]) { // Set global variables _globalThis.Zotero = basicTool.getGlobal("Zotero"); - _globalThis.ZoteroPane = basicTool.getGlobal("ZoteroPane"); - _globalThis.Zotero_Tabs = basicTool.getGlobal("Zotero_Tabs"); - _globalThis.window = basicTool.getGlobal("window"); - _globalThis.document = basicTool.getGlobal("document"); + defineGlobal("window"); + defineGlobal("document"); + defineGlobal("ZoteroPane"); + defineGlobal("Zotero_Tabs"); _globalThis.addon = new Addon(); _globalThis.ztoolkit = addon.data.ztoolkit; - ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`; - ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production"; - ztoolkit.UI.basicOptions.ui.enableElementJSONLog = - addon.data.env === "development"; - ztoolkit.UI.basicOptions.ui.enableElementDOMLog = - addon.data.env === "development"; - ztoolkit.basicOptions.debug.disableDebugBridgePassword = - addon.data.env === "development"; Zotero[config.addonInstance] = addon; // Trigger addon hook for initialization addon.hooks.onStartup(); } + +function defineGlobal(name: Parameters[0]) { + Object.defineProperty(_globalThis, name, { + get() { + return basicTool.getGlobal(name); + }, + }); +} diff --git a/src/utils/ztoolkit.ts b/src/utils/ztoolkit.ts new file mode 100644 index 0000000..dd7035c --- /dev/null +++ b/src/utils/ztoolkit.ts @@ -0,0 +1,48 @@ +import ZoteroToolkit from "zotero-plugin-toolkit"; +import { config } from "../../package.json"; + +export { createZToolkit }; + +function createZToolkit() { + const _ztoolkit = new ZoteroToolkit(); + /** + * Alternatively, import toolkit modules you use to minify the plugin size. + * You can add the modules under the `MyToolkit` class below and uncomment the following line. + */ + // const _ztoolkit = new MyToolkit(); + initZToolkit(_ztoolkit); + return _ztoolkit; +} + +function initZToolkit(_ztoolkit: ReturnType) { + const env = __env__; + _ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`; + _ztoolkit.basicOptions.log.disableConsole = env === "production"; + _ztoolkit.UI.basicOptions.ui.enableElementJSONLog = __env__ === "development"; + _ztoolkit.UI.basicOptions.ui.enableElementDOMLog = __env__ === "development"; + _ztoolkit.basicOptions.debug.disableDebugBridgePassword = + __env__ === "development"; + _ztoolkit.ProgressWindow.setIconURI( + "default", + `chrome://${config.addonRef}/content/icons/favicon.png`, + ); +} + +import { BasicTool, unregister } from "zotero-plugin-toolkit/dist/basic"; +import { UITool } from "zotero-plugin-toolkit/dist/tools/ui"; +import { PreferencePaneManager } from "zotero-plugin-toolkit/dist/managers/preferencePane"; + +class MyToolkit extends BasicTool { + UI: UITool; + PreferencePane: PreferencePaneManager; + + constructor() { + super(); + this.UI = new UITool(this); + this.PreferencePane = new PreferencePaneManager(this); + } + + unregisterAll() { + unregister(this); + } +} diff --git a/typings/global.d.ts b/typings/global.d.ts index 8906d0b..34e9688 100644 --- a/typings/global.d.ts +++ b/typings/global.d.ts @@ -5,12 +5,15 @@ declare const _globalThis: { Zotero_Tabs: typeof Zotero_Tabs; window: Window; document: Document; - ztoolkit: typeof ztoolkit; + ztoolkit: ZToolkit; addon: typeof addon; }; -// declare const ztoolkit: import("../src/addon").MyToolkit; -declare const ztoolkit: import("zotero-plugin-toolkit").ZoteroToolkit; +declare type ZToolkit = ReturnType< + typeof import("../src/utils/ztoolkit").createZToolkit +>; + +declare const ztoolkit: ZToolkit; declare const rootURI: string;