diff --git a/calendar/experiments/calendar/parent/ext-calendar-items.js b/calendar/experiments/calendar/parent/ext-calendar-items.js index f36b5b0..ac762b8 100644 --- a/calendar/experiments/calendar/parent/ext-calendar-items.js +++ b/calendar/experiments/calendar/parent/ext-calendar-items.js @@ -5,10 +5,560 @@ var { ExtensionCommon: { ExtensionAPI, EventManager } } = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); var { ExtensionUtils: { ExtensionError } } = ChromeUtils.importESModule("resource://gre/modules/ExtensionUtils.sys.mjs"); +var { ExtensionSupport } = ChromeUtils.importESModule("resource:///modules/ExtensionSupport.sys.mjs"); var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs"); +const EVENT_DIALOG_URL = "chrome://calendar/content/calendar-event-dialog.xhtml"; +const MESSENGER_URL = "chrome://messenger/content/messenger.xhtml"; + this.calendar_items = class extends ExtensionAPI { + _ensureEditorClosedListenerSet() { + if (!this._editorClosedListeners) { + this._editorClosedListeners = new Set(); + } + return this._editorClosedListeners; + } + + _ensureLifecycleStateMap() { + if (!this._editorLifecycleByTarget) { + this._editorLifecycleByTarget = new WeakMap(); + } + return this._editorLifecycleByTarget; + } + + _addEditorClosedListener(listener) { + this._ensureEditorClosedListenerSet().add(listener); + } + + _removeEditorClosedListener(listener) { + this._ensureEditorClosedListenerSet().delete(listener); + } + + _emitEditorClosed(info) { + const listeners = this._ensureEditorClosedListenerSet(); + for (const listener of listeners) { + try { + listener(info); + } catch (e) { + console.error("[calendar.items] onEditorClosed listener failed", e); + } + } + } + + _makeEditorKey(editorRef) { + const ref = editorRef && typeof editorRef == "object" ? editorRef : {}; + if (typeof ref.dialogOuterId == "number") { + return `dialog:${ref.dialogOuterId}`; + } + if (typeof ref.tabId == "number") { + return `tab:${ref.tabId}`; + } + if (typeof ref.windowId == "number") { + return `window:${ref.windowId}`; + } + return ""; + } + + _isWindowManagerType(windowType) { + switch (windowType) { + case "mail:3pane": + case "msgcompose": + case "mail:messageWindow": + case "mail:extensionPopup": + return true; + default: + return false; + } + } + + _getManagedWindowId(context, window) { + try { + const manager = context?.extension?.windowManager; + if (!manager || typeof manager.getWrapper != "function") { + return null; + } + const windowType = window?.document?.documentElement?.getAttribute?.("windowtype") || ""; + if (!this._isWindowManagerType(windowType)) { + return null; + } + const wrapper = manager.getWrapper(window); + const id = wrapper?.id; + return typeof id == "number" ? id : null; + } catch (e) { + console.error("[calendar.items] get managed window id failed", e); + return null; + } + } + + _getManagedTabId(context, window) { + try { + if (!window || window.location?.href != MESSENGER_URL) { + return null; + } + const nativeTab = window.tabmail?.currentTabInfo?.nativeTab || null; + if (!nativeTab) { + return null; + } + const manager = context?.extension?.tabManager; + if (!manager || typeof manager.getWrapper != "function") { + return null; + } + const wrapper = manager.getWrapper(nativeTab); + const id = wrapper?.id; + return typeof id == "number" ? id : null; + } catch (e) { + console.error("[calendar.items] get managed tab id failed", e); + return null; + } + } + + _getDialogOuterId(window) { + try { + const windowType = window?.document?.documentElement?.getAttribute?.("windowtype") || ""; + if (windowType != "Calendar:EventDialog" && windowType != "Calendar:EventSummaryDialog") { + return null; + } + const outerId = window?.docShell?.outerWindowID ?? window?.windowUtils?.outerWindowID; + return typeof outerId == "number" ? outerId : null; + } catch (e) { + console.error("[calendar.items] get dialog outer id failed", e); + return null; + } + } + + _buildEditorRef(context, window) { + const editorRef = {}; + + const tabId = this._getManagedTabId(context, window); + if (typeof tabId == "number") { + editorRef.tabId = tabId; + } + + const windowId = this._getManagedWindowId(context, window); + if (typeof windowId == "number") { + editorRef.windowId = windowId; + } + + const dialogOuterId = this._getDialogOuterId(window); + if (typeof dialogOuterId == "number") { + editorRef.dialogOuterId = dialogOuterId; + } + + return Object.keys(editorRef).length ? editorRef : null; + } + + _resolveEditorWindow(context, editorRef) { + const ref = editorRef && typeof editorRef == "object" ? editorRef : {}; + + if (typeof ref.dialogOuterId == "number") { + if (!Services?.wm?.getOuterWindowWithId) { + // continue with other ids + } else { + try { + const win = Services.wm.getOuterWindowWithId(ref.dialogOuterId); + if (win && !win.closed) { + return win; + } + } catch (e) { + console.error("[calendar.items] resolve dialog window failed", e); + } + } + } + + if (typeof ref.tabId == "number") { + const tabManager = context?.extension?.tabManager; + if (tabManager && typeof tabManager.get == "function") { + try { + const tabWrapper = tabManager.get(ref.tabId); + const nativeTab = tabWrapper?.nativeTab || null; + const win = nativeTab?.ownerGlobal || null; + if (win && !win.closed) { + return win; + } + } catch (e) { + console.error("[calendar.items] resolve tab id failed", e); + } + } + } + + if (typeof ref.windowId == "number") { + const manager = context?.extension?.windowManager; + if (manager && typeof manager.get == "function") { + try { + const winObj = manager.get(ref.windowId); + const win = winObj?.window || null; + if (win && !win.closed) { + return win; + } + } catch (e) { + console.error("[calendar.items] resolve window id failed", e); + } + } + } + + return null; + } + + _resolveSnapshotWindow(context, editorRef) { + const resolved = this._resolveEditorWindow(context, editorRef); + if (resolved) { + return resolved; + } + + try { + const browsingContextWindow = context?.browsingContext?.embedderElement?.ownerGlobal || null; + if (browsingContextWindow && !browsingContextWindow.closed) { + return browsingContextWindow; + } + } catch (e) { + console.error("[calendar.items] resolve browsing context window failed", e); + } + return null; + } + + _getEditedItemForWindow(window) { + if (!window || !window.location) { + return null; + } + + if (window.location.href.startsWith(EVENT_DIALOG_URL)) { + const fromWindow = win => { + if (!win) { + return null; + } + if (win.calendarItem) { + return win.calendarItem; + } + if (win.gEvent?.event) { + return win.gEvent.event; + } + const arg0 = Array.isArray(win.arguments) ? win.arguments[0] : null; + if (arg0?.calendarItem) { + return arg0.calendarItem; + } + if (arg0?.calendarEvent) { + return arg0.calendarEvent; + } + return null; + }; + + const direct = fromWindow(window); + if (direct) { + return direct; + } + + const panelIframe = window.document?.getElementById?.("calendar-item-panel-iframe") || null; + const panelWin = panelIframe?.contentWindow || panelIframe?.contentDocument?.defaultView || null; + return fromWindow(panelWin); + } + + if (window.location.href.startsWith(MESSENGER_URL)) { + const tabInfo = window.tabmail?.currentTabInfo || null; + if (tabInfo?.mode?.name != "calendarEvent") { + return null; + } + return tabInfo.iframe?.contentWindow?.calendarItem || null; + } + + return null; + } + + _getLifecycleTargetWindow(window) { + if (!window || !window.location) { + return null; + } + if (window.location.href.startsWith(EVENT_DIALOG_URL)) { + return window; + } + if (window.location.href.startsWith(MESSENGER_URL)) { + const tabInfo = window.tabmail?.currentTabInfo || null; + if (tabInfo?.mode?.name != "calendarEvent") { + return null; + } + return tabInfo.iframe?.contentWindow || tabInfo.iframe?.contentDocument?.defaultView || null; + } + return null; + } + + _cleanupLifecycleState(target) { + if (!target) { + return; + } + + const stateMap = this._ensureLifecycleStateMap(); + const state = stateMap.get(target); + if (!state) { + return; + } + stateMap.delete(target); + + const cleanup = Array.isArray(state.cleanup) ? state.cleanup : []; + while (cleanup.length) { + const fn = cleanup.pop(); + try { + fn(); + } catch (e) { + console.error("[calendar.items] cleanup lifecycle state failed", e); + } + } + } + + _cleanupLifecycleInWindow(window) { + if (!window || !window.location) { + return; + } + + if (window.location.href.startsWith(EVENT_DIALOG_URL)) { + this._cleanupLifecycleState(window); + return; + } + + if (!window.location.href.startsWith(MESSENGER_URL)) { + return; + } + + const tabmail = window.tabmail; + const tabInfoList = tabmail && Array.isArray(tabmail.tabInfo) ? tabmail.tabInfo : []; + for (const tabInfo of tabInfoList) { + if (tabInfo?.mode?.name != "calendarEvent") { + continue; + } + const target = tabInfo.iframe?.contentWindow || tabInfo.iframe?.contentDocument?.defaultView || null; + this._cleanupLifecycleState(target); + } + } + + _ensureLifecycleWatch(context, window) { + const target = this._getLifecycleTargetWindow(window); + if (!target) { + return; + } + + const stateMap = this._ensureLifecycleStateMap(); + const nextEditorRef = this._buildEditorRef(context, window); + const nextEditorKey = this._makeEditorKey(nextEditorRef); + const previous = stateMap.get(target); + if (previous) { + if (previous.editorKey == nextEditorKey) { + return; + } + this._emitEditorClosed({ + editorRef: previous.editorRef || {}, + action: "superseded", + reason: "re-bound" + }); + this._cleanupLifecycleState(target); + } + + const state = { + editorRef: nextEditorRef || {}, + editorKey: nextEditorKey, + cleanup: [], + closed: false + }; + stateMap.set(target, state); + + const emitOnce = (action, reason) => { + if (state.closed) { + return; + } + state.closed = true; + this._emitEditorClosed({ + editorRef: state.editorRef || {}, + action, + reason: reason || "" + }); + this._cleanupLifecycleState(target); + }; + + const addListener = (type, handler, options) => { + target.addEventListener(type, handler, options); + state.cleanup.push(() => { + target.removeEventListener(type, handler, options); + }); + }; + + const isDialog = !!(target.location?.href || "").startsWith(EVENT_DIALOG_URL); + if (isDialog) { + addListener("dialogaccept", () => emitOnce("persisted", "dialogaccept"), true); + addListener("dialogextra1", () => emitOnce("persisted", "dialogextra1"), true); + addListener("dialogcancel", () => emitOnce("discarded", "dialogcancel"), true); + addListener("dialogextra2", () => emitOnce("discarded", "dialogextra2"), true); + } + + addListener("unload", () => emitOnce("discarded", "unload"), true); + } + + _collectEventDocs(window) { + const docs = []; + const pushDoc = doc => { + if (!doc || docs.includes(doc)) { + return; + } + docs.push(doc); + }; + + pushDoc(window?.document || null); + + if (window?.location?.href?.startsWith(EVENT_DIALOG_URL)) { + const iframe = window.document?.getElementById?.("calendar-item-panel-iframe") || null; + pushDoc(iframe?.contentDocument || null); + } + + if (window?.location?.href?.startsWith(MESSENGER_URL)) { + const tabInfo = window.tabmail?.currentTabInfo || null; + if (tabInfo?.mode?.name == "calendarEvent") { + pushDoc(tabInfo.iframe?.contentDocument || null); + } + } + + return docs; + } + + _findField(docs, selectors) { + for (const doc of docs) { + if (!doc || typeof doc.querySelector != "function") { + continue; + } + for (const selector of selectors) { + const element = doc.querySelector(selector); + if (element) { + return element; + } + } + } + return null; + } + + _findDescriptionFieldInDocs(docs) { + for (const doc of docs) { + const host = doc?.querySelector?.("editor#item-description") || null; + if (host) { + const target = host.inputField || host.contentDocument?.body || host; + if (target) { + return target; + } + } + const fallback = doc?.querySelector?.("textarea#item-description") || null; + if (fallback) { + return fallback; + } + } + return null; + } + + _dispatchInputEvent(field) { + if (!field) { + return; + } + const doc = field.ownerDocument || field.document; + const win = doc?.defaultView; + if (win) { + field.dispatchEvent(new win.Event("input", { bubbles: true })); + } + } + + _setFieldValue(field, value, opts = {}) { + if (!field) { + return; + } + + const doc = field.ownerDocument || field.document || field.contentDocument || null; + const preferExec = opts.preferExec === true; + const tryExecCommand = () => { + if (!doc || typeof doc.execCommand != "function") { + return false; + } + field.focus?.(); + doc.execCommand("selectAll", false, null); + doc.execCommand("insertText", false, value); + return true; + }; + + if (preferExec && tryExecCommand()) { + this._dispatchInputEvent(field); + return; + } + + if ("value" in field) { + field.focus?.(); + field.value = value; + this._dispatchInputEvent(field); + return; + } + + if ((field.isContentEditable || field.tagName?.toLowerCase?.() == "body") && tryExecCommand()) { + this._dispatchInputEvent(field); + return; + } + + if (field.textContent !== undefined) { + field.textContent = value; + this._dispatchInputEvent(field); + } + } + + _applyFieldUpdates(window, fields) { + const docs = this._collectEventDocs(window); + const titleField = this._findField(docs, ["#item-title"]); + const locationField = this._findField(docs, ["#item-location"]); + const descField = this._findDescriptionFieldInDocs(docs); + + const applied = { + title: false, + location: false, + description: false + }; + + if (typeof fields.title == "string" && titleField) { + this._setFieldValue(titleField, fields.title); + applied.title = true; + } + if (typeof fields.location == "string" && locationField) { + this._setFieldValue(locationField, fields.location); + applied.location = true; + } + if (typeof fields.description == "string" && descField) { + this._setFieldValue(descField, fields.description, { preferExec: true }); + applied.description = true; + } + + return applied; + } + + _applyPropertyUpdates(item, properties) { + for (const [name, value] of Object.entries(properties || {})) { + if (!name) { + continue; + } + if (value == null || value == "") { + if (typeof item.deleteProperty == "function") { + item.deleteProperty(name); + } else { + item.setProperty(name, ""); + } + } else { + item.setProperty(name, String(value)); + } + } + } + + onShutdown() { + for (const window of ExtensionSupport.openWindows) { + try { + this._cleanupLifecycleInWindow(window); + } catch (e) { + console.error("[calendar.items] shutdown cleanup failed", e); + } + } + + if (this._editorClosedListeners) { + this._editorClosedListeners.clear(); + } + } + getAPI(context) { + const api = this; const uuid = context.extension.uuid; const root = `experiments-calendar-${uuid}`; const query = context.extension.manifest.version; @@ -150,14 +700,48 @@ this.calendar_items = class extends ExtensionAPI { }, async getCurrent(options) { - try { - // TODO This seems risky, could be null depending on remoteness - const item = context.browsingContext.embedderElement.ownerGlobal.calendarItem; - return convertItem(item, options, context.extension); - } catch (e) { - console.error(e); + let win = api._resolveSnapshotWindow(context, options?.editorRef); + if (!win) { return null; } + let item = api._getEditedItemForWindow(win); + if (!item) { + return null; + } + api._ensureLifecycleWatch(context, win); + const converted = convertItem(item, options, context.extension); + if (converted) { + const editorRef = api._buildEditorRef(context, win); + if (editorRef) { + converted.editorRef = editorRef; + } + } + return converted; + }, + + async updateCurrent(updateOptions) { + let win = api._resolveSnapshotWindow(context, updateOptions?.editorRef); + if (!win) { + throw new ExtensionError("Could not resolve target editor window"); + } + let item = api._getEditedItemForWindow(win); + if (!item) { + throw new ExtensionError("Could not find current editor item"); + } + api._ensureLifecycleWatch(context, win); + + const fields = updateOptions?.fields && typeof updateOptions.fields == "object" ? updateOptions.fields : {}; + const properties = updateOptions?.properties && typeof updateOptions.properties == "object" ? updateOptions.properties : {}; + api._applyFieldUpdates(win, fields); + api._applyPropertyUpdates(item, properties); + const converted = convertItem(item, updateOptions, context.extension); + if (converted) { + const editorRef = api._buildEditorRef(context, win); + if (editorRef) { + converted.editorRef = editorRef; + } + } + return converted; }, onCreated: new EventManager({ @@ -237,6 +821,20 @@ this.calendar_items = class extends ExtensionAPI { }; }, }).api(), + + onEditorClosed: new EventManager({ + context, + name: "calendar.items.onEditorClosed", + register: fire => { + const listener = info => { + fire.sync(info); + }; + api._addEditorClosedListener(listener); + return () => { + api._removeEditorClosedListener(listener); + }; + }, + }).api(), }, }, }; diff --git a/calendar/experiments/calendar/parent/ext-calendarItemAction.js b/calendar/experiments/calendar/parent/ext-calendarItemAction.js index 3dcaed2..af344c0 100644 --- a/calendar/experiments/calendar/parent/ext-calendarItemAction.js +++ b/calendar/experiments/calendar/parent/ext-calendarItemAction.js @@ -2,13 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -var { ExtensionCommon: { makeWidgetId } } = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); - var { ExtensionParent } = ChromeUtils.importESModule("resource://gre/modules/ExtensionParent.sys.mjs"); var { ExtensionSupport } = ChromeUtils.importESModule("resource:///modules/ExtensionSupport.sys.mjs"); var { ToolbarButtonAPI } = ChromeUtils.importESModule("resource:///modules/ExtensionToolbarButtons.sys.mjs"); const calendarItemActionMap = new WeakMap(); +const CALITEM_EVENT_DIALOG_URL = "chrome://calendar/content/calendar-event-dialog.xhtml"; +const CALITEM_MESSENGER_URL = "chrome://messenger/content/messenger.xhtml"; this.calendarItemAction = class extends ToolbarButtonAPI { static for(extension) { @@ -57,9 +57,14 @@ this.calendarItemAction = class extends ToolbarButtonAPI { // Core code only works for one toolbox/toolbarId. Calendar uses different ones. When porting // you can leave all of this out by either using the same ids, or adapting parent class code to // deal with ids per window url. - if (this.extension.startupReason == "ADDON_INSTALL") { - // Add it to the messenger window, the other one is already covered by parent code. + if ( + this.extension.startupReason == "ADDON_INSTALL" || + this.extension.startupReason == "ADDON_UPGRADE" + ) { + // Ensure both editor variants have the button in persisted toolbar sets + // on fresh install and profile migrations during add-on upgrade. this.addToCurrentSet("chrome://messenger/content/messenger.xhtml", "event-tab-toolbar"); + this.addToCurrentSet("chrome://calendar/content/calendar-event-dialog.xhtml", "event-toolbar"); } } @@ -106,7 +111,7 @@ this.calendarItemAction = class extends ToolbarButtonAPI { // This is only necessary as part of the experiment, refactor when moving to core. paint(window) { - if (window.location.href == "chrome://calendar/content/calendar-event-dialog.xhtml") { + if (window.location.href == CALITEM_EVENT_DIALOG_URL) { this.toolbarId = "event-toolbar"; } else { this.toolbarId = "event-tab-toolbar"; @@ -114,9 +119,82 @@ this.calendarItemAction = class extends ToolbarButtonAPI { return super.paint(window); } + _getDialogOuterId(window) { + const outerId = window?.docShell?.outerWindowID ?? window?.windowUtils?.outerWindowID; + return typeof outerId == "number" ? outerId : null; + } + + _getEditorClickContext(window) { + const href = window?.location?.href || ""; + if (href == CALITEM_EVENT_DIALOG_URL) { + const dialogOuterId = this._getDialogOuterId(window); + const editorRef = {}; + if (typeof dialogOuterId == "number") { + editorRef.dialogOuterId = dialogOuterId; + } + return { + editorType: "dialog", + editorRef, + }; + } + + if (href == CALITEM_MESSENGER_URL) { + const tabInfo = window.tabmail?.currentTabInfo || null; + if (tabInfo?.mode?.name == "calendarEvent") { + const editorRef = {}; + const nativeTab = tabInfo.nativeTab || null; + const tabManager = this.extension?.tabManager; + if (tabManager && typeof tabManager.getWrapper == "function" && nativeTab) { + const tabWrapper = tabManager.getWrapper(nativeTab); + const tabId = tabWrapper?.id; + if (typeof tabId == "number") { + editorRef.tabId = tabId; + } + } + const windowManager = this.extension?.windowManager; + if (windowManager && typeof windowManager.getWrapper == "function") { + const windowWrapper = windowManager.getWrapper(window); + const windowId = windowWrapper?.id; + if (typeof windowId == "number") { + editorRef.windowId = windowId; + } + } + return { + editorType: "tab", + editorRef, + }; + } + } + + return { + editorType: "unknown", + editorRef: {}, + }; + } + handleEvent(event) { - super.handleEvent(event); const window = event.target.ownerGlobal; + if (event.type == "mousedown" && event.button == 0) { + if ( + event.target.tagName == "menu" || + event.target.tagName == "menuitem" || + event.target.getAttribute("type") == "menu" + ) { + return; + } + + const clickContext = this._getEditorClickContext(window); + this.lastClickInfo = { + button: 0, + modifiers: this.global.clickModifiersFromEvent(event), + editorType: clickContext.editorType, + editorRef: clickContext.editorRef, + }; + this.triggerAction(window); + return; + } + + super.handleEvent(event); switch (event.type) { case "popupshowing": { @@ -141,37 +219,15 @@ this.calendarItemAction = class extends ToolbarButtonAPI { } } - onShutdown() { + onShutdown(isAppShutdown) { + if (isAppShutdown) { + return; + } + // TODO browserAction uses static onUninstall, this doesn't work in an experiment. + // Do not mutate xulStore during shutdown to preserve user toolbar customizations on upgrades. const extensionId = this.extension.id; ExtensionSupport.unregisterWindowListener("ext-calendar-itemAction-" + extensionId); - - const widgetId = makeWidgetId(extensionId); - const id = `${widgetId}-calendarItemAction-toolbarbutton`; - - const windowURLs = [ - "chrome://messenger/content/messenger.xhtml", - "chrome://calendar/content/calendar-event-dialog.xhtml" - ]; - - for (const windowURL of windowURLs) { - let currentSet = Services.xulStore.getValue( - windowURL, - "event-toolbar", - "currentset" - ); - currentSet = currentSet.split(","); - const index = currentSet.indexOf(id); - if (index >= 0) { - currentSet.splice(index, 1); - Services.xulStore.setValue( - windowURL, - "event-toolbar", - "currentset", - currentSet.join(",") - ); - } - } } }; diff --git a/calendar/experiments/calendar/schema/calendar-items.json b/calendar/experiments/calendar/schema/calendar-items.json index bc44788..97368d1 100644 --- a/calendar/experiments/calendar/schema/calendar-items.json +++ b/calendar/experiments/calendar/schema/calendar-items.json @@ -13,6 +13,7 @@ "instance": { "type": "string", "optional": true }, "format": { "$ref": "CalendarItemFormats", "optional": true }, "item": { "$ref": "RawCalendarItem" }, + "editorRef": { "$ref": "EditorRef", "optional": true }, "metadata": { "type": "object", "additionalProperties": { "type": "any" }, "optional": true } } }, @@ -43,6 +44,49 @@ "offset": { "type": "string" }, "related": { "type": "string", "enum": ["absolute", "start", "end"] } } + }, + { + "id": "EditorRef", + "type": "object", + "description": "Reference to an open calendar editor context.", + "properties": { + "tabId": { "type": "integer", "optional": true }, + "windowId": { "type": "integer", "optional": true }, + "dialogOuterId": { "type": "integer", "optional": true } + } + }, + { + "id": "EditorFieldUpdates", + "type": "object", + "properties": { + "title": { "type": "string", "optional": true }, + "location": { "type": "string", "optional": true }, + "description": { "type": "string", "optional": true } + } + }, + { + "id": "EditorPropertyUpdates", + "type": "object", + "additionalProperties": { + "choices": [ + { "type": "string" }, + { "type": "null" } + ] + } + }, + { + "id": "EditorClosedAction", + "type": "string", + "enum": ["persisted", "discarded", "superseded"] + }, + { + "id": "EditorClosedInfo", + "type": "object", + "properties": { + "editorRef": { "$ref": "EditorRef" }, + "action": { "$ref": "EditorClosedAction" }, + "reason": { "type": "string", "optional": true } + } } ], "functions": [ @@ -157,6 +201,24 @@ "name": "getOptions", "optional": true, "properties": { + "returnFormat": { "$ref": "ReturnFormat", "optional": true }, + "editorRef": { "$ref": "EditorRef", "optional": true } + } + } + ] + }, + { + "name": "updateCurrent", + "async": true, + "type": "function", + "parameters": [ + { + "type": "object", + "name": "updateOptions", + "properties": { + "editorRef": { "$ref": "EditorRef", "optional": true }, + "fields": { "$ref": "EditorFieldUpdates", "optional": true }, + "properties": { "$ref": "EditorPropertyUpdates", "optional": true }, "returnFormat": { "$ref": "ReturnFormat", "optional": true } } } @@ -221,6 +283,13 @@ } } ] + }, + { + "name": "onEditorClosed", + "type": "function", + "parameters": [ + { "name": "info", "$ref": "EditorClosedInfo" } + ] } ] } diff --git a/calendar/experiments/calendar/schema/calendarItemAction.json b/calendar/experiments/calendar/schema/calendarItemAction.json index 52fd362..0631183 100644 --- a/calendar/experiments/calendar/schema/calendarItemAction.json +++ b/calendar/experiments/calendar/schema/calendarItemAction.json @@ -98,6 +98,28 @@ "^[1-9]\\d*$": {"$ref": "ImageDataType"} } }, + { + "id": "EditorRef", + "type": "object", + "description": "Reference to the currently active event editor context.", + "properties": { + "tabId": { + "type": "integer", + "optional": true, + "description": "The extension tab id for tab based event editors." + }, + "windowId": { + "type": "integer", + "optional": true, + "description": "The extension window id for tab based editors." + }, + "dialogOuterId": { + "type": "integer", + "optional": true, + "description": "The native outer window id for dialog based editors." + } + } + }, { "id": "OnClickData", "type": "object", @@ -115,6 +137,17 @@ "type": "integer", "optional": true, "description": "An integer value of button by which menu item was clicked." + }, + "editorRef": { + "$ref": "EditorRef", + "optional": true, + "description": "Reference to the editor context where the action was invoked." + }, + "editorType": { + "type": "string", + "enum": ["dialog", "tab", "unknown"], + "optional": true, + "description": "Editor variant where the action was invoked." } } } @@ -651,4 +684,3 @@ ] } ] -