diff --git a/calendar/README.md b/calendar/README.md index f3fa8c4..f94ace2 100644 --- a/calendar/README.md +++ b/calendar/README.md @@ -12,5 +12,5 @@ reliable API documentation please see the relevant [schema files](./schema/). | ------------- | -------- | Description | Experiment and add-on for calendar-related APIs in Thunderbird. | Status | Draft -| Compatibility | Thunderbird 91 (possibly Thunderbird 78) +| Compatibility | Thunderbird 128 | Tracking | [bug 1627205](https://bugzilla.mozilla.org/show_bug.cgi?id=1627205) diff --git a/calendar/experiments/calendar/child/ext-calendar-provider-actor.jsm b/calendar/experiments/calendar/child/ext-calendar-provider-actor.sys.mjs similarity index 74% rename from calendar/experiments/calendar/child/ext-calendar-provider-actor.jsm rename to calendar/experiments/calendar/child/ext-calendar-provider-actor.sys.mjs index 298fc3f..4322578 100644 --- a/calendar/experiments/calendar/child/ext-calendar-provider-actor.jsm +++ b/calendar/experiments/calendar/child/ext-calendar-provider-actor.sys.mjs @@ -2,12 +2,10 @@ * 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 EXPORTED_SYMBOLS = ["CalendarProviderChild"]; - -class CalendarProviderChild extends JSWindowActorChild { +export class CalendarProviderChild extends JSWindowActorChild { receiveMessage(msg) { if (msg.name == "postMessage") { - this.contentWindow.postMessage(msg.data.message, msg.data.origin) + this.contentWindow.postMessage(msg.data.message, msg.data.origin); } } } diff --git a/calendar/experiments/calendar/ext-calendar-utils.jsm b/calendar/experiments/calendar/ext-calendar-utils.sys.mjs similarity index 77% rename from calendar/experiments/calendar/ext-calendar-utils.jsm rename to calendar/experiments/calendar/ext-calendar-utils.sys.mjs index 33cf825..08cca19 100644 --- a/calendar/experiments/calendar/ext-calendar-utils.jsm +++ b/calendar/experiments/calendar/ext-calendar-utils.sys.mjs @@ -2,52 +2,22 @@ * 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 EXPORTED_SYMBOLS = [ - "isOwnCalendar", - "unwrapCalendar", - "getResolvedCalendarById", - "getCachedCalendar", - "isCachedCalendar", - "convertCalendar", - "propsToItem", - "convertItem", - "convertAlarm", - "setupE10sBrowser", -]; - -var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetters(this, { - cal: "resource:///modules/calendar/calUtils.jsm", - ICAL: "resource:///modules/calendar/Ical.jsm", - CalEvent: "resource:///modules/CalEvent.jsm", - CalTodo: "resource:///modules/CalTodo.jsm", - ExtensionParent: "resource://gre/modules/ExtensionParent.jsm", -}); - -var { ExtensionError, promiseEvent } = ChromeUtils.import( - "resource://gre/modules/ExtensionUtils.jsm" -).ExtensionUtils; - -XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => { - let stylesheets = []; - let { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); - - if (AppConstants.platform === "macosx") { - stylesheets.push("chrome://browser/content/extension-mac-panel.css"); - } else if (AppConstants.platform === "win") { - stylesheets.push("chrome://browser/content/extension-win-panel.css"); - } else if (AppConstants.platform === "linux") { - stylesheets.push("chrome://browser/content/extension-linux-panel.css"); - } - return stylesheets; -}); +var { + ExtensionUtils: { ExtensionError, promiseEvent } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionUtils.sys.mjs"); + +var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs"); +var { CalEvent } = ChromeUtils.importESModule("resource:///modules/CalEvent.sys.mjs"); +var { CalTodo } = ChromeUtils.importESModule("resource:///modules/CalTodo.sys.mjs"); +var { ExtensionParent } = ChromeUtils.importESModule("resource://gre/modules/ExtensionParent.sys.mjs"); + +var { default: ICAL } = ChromeUtils.importESModule("resource:///modules/calendar/Ical.sys.mjs"); -function isOwnCalendar(calendar, extension) { +export function isOwnCalendar(calendar, extension) { return calendar.superCalendar.type == "ext-" + extension.id; } -function unwrapCalendar(calendar) { +export function unwrapCalendar(calendar) { let unwrapped = calendar.wrappedJSObject; if (unwrapped.mUncachedCalendar) { @@ -57,7 +27,7 @@ function unwrapCalendar(calendar) { return unwrapped; } -function getResolvedCalendarById(extension, id) { +export function getResolvedCalendarById(extension, id) { let calendar; if (id.endsWith("#cache")) { let cached = cal.manager.getCalendarById(id.substring(0, id.length - 6)); @@ -72,16 +42,16 @@ function getResolvedCalendarById(extension, id) { return calendar; } -function getCachedCalendar(calendar) { +export function getCachedCalendar(calendar) { return calendar.wrappedJSObject.mCachedCalendar || calendar; } -function isCachedCalendar(id) { +export function isCachedCalendar(id) { // TODO make this better return id.endsWith("#cache"); } -function convertCalendar(extension, calendar) { +export function convertCalendar(extension, calendar) { if (!calendar) { return null; } @@ -105,7 +75,7 @@ function convertCalendar(extension, calendar) { return props; } -function propsToItem(props, baseItem) { +export function propsToItem(props, baseItem) { let item; if (baseItem) { item = baseItem; @@ -122,7 +92,18 @@ function propsToItem(props, baseItem) { if (props.formats?.use == "ical") { item.icalString = props.formats.ical; } else if (props.formats?.use == "jcal") { - item.icalString = ICAL.stringify(props.formats.jcal); + try { + item.icalString = ICAL.stringify(props.formats.jcal); + } catch (e) { + let jsonstring; + try { + jsonstring = JSON.stringify(props.formats.jcal, null, 2); + } catch { + jsonstring = props.formats.jcal; + } + + throw new ExtensionError("Could not parse jCal: " + e + "\n" + jsonstring); + } } else { if (props.id) { item.id = props.id; @@ -155,7 +136,7 @@ function propsToItem(props, baseItem) { return item; } -function convertItem(item, options, extension) { +export function convertItem(item, options, extension) { if (!item) { return null; } @@ -181,7 +162,7 @@ function convertItem(item, options, extension) { try { // TODO This is a sync operation. Not great. Can we optimize this? props.metadata = JSON.parse(cache.getMetaData(item.id)) ?? {}; - } catch (e) { + } catch { // Ignore json parse errors } } @@ -218,7 +199,7 @@ function convertItem(item, options, extension) { return props; } -function convertAlarm(item, alarm) { +export function convertAlarm(item, alarm) { const ALARM_RELATED_MAP = { [Ci.calIAlarm.ALARM_RELATED_ABSOLUTE]: "absolute", [Ci.calIAlarm.ALARM_RELATED_START]: "start", @@ -234,7 +215,7 @@ function convertAlarm(item, alarm) { }; } -async function setupE10sBrowser(extension, browser, parent, initOptions={}) { +export async function setupE10sBrowser(extension, browser, parent, initOptions={}) { browser.setAttribute("type", "content"); browser.setAttribute("disableglobalhistory", "true"); browser.setAttribute("messagemanagergroup", "webext-browsers"); @@ -267,9 +248,10 @@ async function setupE10sBrowser(extension, browser, parent, initOptions={}) { let sheets = []; if (initOptions.browser_style) { delete initOptions.browser_style; - sheets.push(...ExtensionParent.extensionStylesheets); + sheets.push("chrome://browser/content/extension.css"); } - sheets.push(...standaloneStylesheets); + sheets.push("chrome://browser/content/extension-popup-panel.css"); + const initBrowser = () => { ExtensionParent.apiManager.emit("extension-browser-inserted", browser); diff --git a/calendar/experiments/calendar/parent/ext-calendar-calendars.js b/calendar/experiments/calendar/parent/ext-calendar-calendars.js index 2d7db92..a39a481 100644 --- a/calendar/experiments/calendar/parent/ext-calendar-calendars.js +++ b/calendar/experiments/calendar/parent/ext-calendar-calendars.js @@ -2,14 +2,14 @@ * 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 } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); -var { ExtensionUtils } = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm"); +var { + ExtensionCommon: { ExtensionAPI, EventManager } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); +var { + ExtensionUtils: { ExtensionError } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionUtils.sys.mjs"); -var { ExtensionAPI, EventManager } = ExtensionCommon; -var { ExtensionError } = ExtensionUtils; - -var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); -var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs"); this.calendar_calendars = class extends ExtensionAPI { getAPI(context) { @@ -18,7 +18,7 @@ this.calendar_calendars = class extends ExtensionAPI { getResolvedCalendarById, isOwnCalendar, convertCalendar, - } = ChromeUtils.import("resource://experiment-calendar/experiments/calendar/ext-calendar-utils.jsm"); + } = ChromeUtils.importESModule("resource://tb-experiments-calendar/experiments/calendar/ext-calendar-utils.sys.mjs"); return { calendar: { @@ -30,7 +30,7 @@ this.calendar_calendars = class extends ExtensionAPI { if (url) { try { pattern = new MatchPattern(url, { restrictSchemes: false }); - } catch (e) { + } catch { throw new ExtensionError(`Invalid url pattern: ${url}`); } } @@ -127,7 +127,18 @@ this.calendar_calendars = class extends ExtensionAPI { if (updateProperties.capabilities) { // TODO validate capability names - calendar.capabilities = Object.assign({}, calendar.capabilities, updateProperties.capabilities); + let unwrappedCalendar = calendar.wrappedJSObject.mUncachedCalendar.wrappedJSObject; + unwrappedCalendar.capabilities = Object.assign({}, unwrappedCalendar.capabilities, updateProperties.capabilities); + } + + if (updateProperties.lastError !== undefined) { + if (updateProperties.lastError === null) { + calendar.setProperty("currentStatus", Cr.NS_ERROR_FAILURE); + calendar.setProperty("lastErrorMessage", updateProperties.lastError); + } else { + calendar.setProperty("currentStatus", Cr.NS_OK); + calendar.setProperty("lastErrorMessage", ""); + } } }, remove: async function(id) { diff --git a/calendar/experiments/calendar/parent/ext-calendar-items.js b/calendar/experiments/calendar/parent/ext-calendar-items.js index 4880e59..73e349e 100644 --- a/calendar/experiments/calendar/parent/ext-calendar-items.js +++ b/calendar/experiments/calendar/parent/ext-calendar-items.js @@ -2,12 +2,14 @@ * 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 } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); -var { ExtensionUtils } = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm"); -var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { + ExtensionCommon: { ExtensionAPI, EventManager } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); +var { + ExtensionUtils: { ExtensionError } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionUtils.sys.mjs"); -var { ExtensionAPI, EventManager } = ExtensionCommon; -var { ExtensionError } = ExtensionUtils; +var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs"); this.calendar_items = class extends ExtensionAPI { getAPI(context) { @@ -19,13 +21,12 @@ this.calendar_items = class extends ExtensionAPI { propsToItem, convertItem, convertAlarm, - } = ChromeUtils.import("resource://experiment-calendar/experiments/calendar/ext-calendar-utils.jsm"); + } = ChromeUtils.importESModule("resource://tb-experiments-calendar/experiments/calendar/ext-calendar-utils.sys.mjs"); return { calendar: { items: { query: async function(queryProps) { - console.log(queryProps); let calendars = []; if (typeof queryProps.calendarId == "string") { calendars = [getResolvedCalendarById(context.extension, queryProps.calendarId)]; @@ -38,9 +39,7 @@ this.calendar_items = class extends ExtensionAPI { let calendarItems; if (queryProps.id) { - calendarItems = await Promise.all(calendars.map(calendar => { - return calendar.getItem(queryProps.id); - })); + calendarItems = await Promise.all(calendars.map(calendar => calendar.getItem(queryProps.id))); } else { calendarItems = await Promise.all(calendars.map(async calendar => { let filter = Ci.calICalendar.ITEM_FILTER_COMPLETED_ALL; @@ -58,17 +57,16 @@ this.calendar_items = class extends ExtensionAPI { let rangeStart = queryProps.rangeStart ? cal.createDateTime(queryProps.rangeStart) : null; let rangeEnd = queryProps.rangeEnd ? cal.createDateTime(queryProps.rangeEnd) : null; + return calendar.getItemsAsArray(filter, queryProps.limit ?? 0, rangeStart, rangeEnd); })); - calendarItems = calendarItems.flat(); } - return calendarItems.map(item => convertItem(item, queryProps, context.extension)); + return calendarItems.flat().map(item => convertItem(item, queryProps, context.extension)); }, get: async function(calendarId, id, options) { let calendar = getResolvedCalendarById(context.extension, calendarId); let item = await calendar.getItem(id); - return convertItem(item, options, context.extension); }, create: async function(calendarId, createProperties) { @@ -139,6 +137,7 @@ this.calendar_items = class extends ExtensionAPI { }, remove: async function(calendarId, id) { let calendar = getResolvedCalendarById(context.extension, calendarId); + let item = await calendar.getItem(id); if (!item) { throw new ExtensionError("Could not find item " + id); diff --git a/calendar/experiments/calendar/parent/ext-calendar-provider.js b/calendar/experiments/calendar/parent/ext-calendar-provider.js index 5fb158f..550dbfc 100644 --- a/calendar/experiments/calendar/parent/ext-calendar-provider.js +++ b/calendar/experiments/calendar/parent/ext-calendar-provider.js @@ -2,17 +2,35 @@ * 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 } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); -var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); -var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); +var { + ExtensionCommon: { ExtensionAPI, EventManager } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); -var { ExtensionAPI, EventManager } = ExtensionCommon; +var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs"); +var { ExtensionSupport } = ChromeUtils.importESModule("resource:///modules/ExtensionSupport.sys.mjs"); -ChromeUtils.defineModuleGetter( - this, - "ExtensionSupport", - "resource:///modules/ExtensionSupport.jsm", -); +// TODO move me +class ItemError extends Error { + static CONFLICT = "CONFLICT"; + static READ_FAILED = "READ_FAILED"; + static MODIFY_FAILED = "MODIFY_FAILED"; + + constructor(reason) { + super(); + this.reason = reason; + } + + get xpcomReason() { + switch (this.reason) { + case ItemError.READ_FAILED: + return Ci.calIErrors.READ_FAILED; + case ItemError.MODIFY_FAILED: + return Ci.calIErrors.MODIFICATION_FAILED; + default: + return Cr.NS_ERROR_FAILURE; + } + } +} function convertProps(props, extension) { let calendar = new ExtCalendar(extension); @@ -30,10 +48,9 @@ class ExtCalendarProvider { QueryInterface = ChromeUtils.generateQI(["calICalendarProvider"]); static register(extension) { - let calmgr = cal.manager; let type = "ext-" + extension.id; - calmgr.registerCalendarProvider( + cal.manager.registerCalendarProvider( type, class extends ExtCalendar { constructor() { @@ -47,15 +64,11 @@ class ExtCalendarProvider { } static unregister(extension) { - let calmgr = cal.manager; let type = "ext-" + extension.id; - calmgr.unregisterCalendarProvider(type, true); + cal.manager.unregisterCalendarProvider(type, true); cal.provider.unregister(type); } - _cachedAdoptItemCallback = null; - _cachedModifyItemCallback = null; - constructor(extension) { this.extension = extension; } @@ -70,10 +83,10 @@ class ExtCalendarProvider { } createCalendar() { - throw new Error("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED); + throw new Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED); } deleteCalendar() { - throw new Error("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED); + throw new Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED); } getCalendar(url) { @@ -161,7 +174,6 @@ class ExtCalendar extends cal.provider.BaseClass { } break; - case "readOnly": if (this.capabilities.mutable === false) { return true; @@ -208,10 +220,13 @@ class ExtCalendar extends cal.provider.BaseClass { return super.getProperty(name); } - async addItem(aItem) { + _cachedAdoptItemCallback = null; + + addItem(aItem) { return this.adoptItem(aItem.clone()); } async adoptItem(aItem) { + const adoptCallback = this._cachedAdoptItemCallback; try { let items = await this.extension.emit("calendar.provider.onItemCreated", this, aItem); let { item, metadata } = items.find(props => props.item) || {}; @@ -227,46 +242,68 @@ class ExtCalendar extends cal.provider.BaseClass { // The ID of the item has changed. We'll have to make sure that whatever old item is in the // cache is removed. // TODO Test this well or risk data loss - await this.deleteItem(aItem); + await this.offlineStorage.deleteItem(aItem); } - if (!item.calendar) { - item.calendar = this.superCalendar; - } + item.calendar = this.superCalendar; + this.observers.notify("onAddItem", [item]); - if (this._cachedAdoptItemCallback) { - await this._cachedAdoptItemCallback( - this.superCalendar, - Cr.NS_OK, - Ci.calIOperationListener.ADD, - item.id, - item - ); + + if (adoptCallback) { + await adoptCallback(item.calendar, Cr.NS_OK, Ci.calIOperationListener.ADD, item.id, item); } return item; } catch (e) { - if (this._cachedAdoptItemCallback) { - await this._cachedAdoptItemCallback( - this.superCalendar, - e.result || Cr.NS_ERROR_FAILURE, - Ci.calIOperationListener.ADD, - aItem.id, - aItem - ); + let code; + if (e instanceof ItemError) { + code = e.xpcomReason; + } else { + code = e.result || Cr.NS_ERROR_FAILURE; + } + + throw new Components.Exception(e.message, code); + } + } + + discoverItem(results) { + let error, success; + + for (let result of results) { + if (typeof result == "object" && result?.error) { + success = null; + error = result.error; + break; + } + + if (typeof result == "object" && result?.item) { + success = result; } - throw e; + // TODO warn if two results? + } + + if (error) { + throw new ItemError(error); + } else { + return success; } } - async modifyItem(aNewItem, aOldItem) { + _cachedModifyItemCallback = null; + + async modifyItem(aNewItem, aOldItem, aOptions = {}) { + const modifyCallback = this._cachedModifyItemCallback; + try { - let items = await this.extension.emit( + let results = await this.extension.emit( "calendar.provider.onItemUpdated", this, aNewItem, - aOldItem + aOldItem, + aOptions ); - let { item, metadata } = items.find(props => props.item) || {}; + + let { item, metadata } = this.discoverItem(results); + if (!item) { throw new Components.Exception("Did not receive item from extension", Cr.NS_ERROR_FAILURE); } @@ -279,40 +316,72 @@ class ExtCalendar extends cal.provider.BaseClass { item.calendar = this.superCalendar; } this.observers.notify("onModifyItem", [item, aOldItem]); - if (this._cachedModifyItemCallback) { - await this._cachedModifyItemCallback( - this.superCalendar, - Cr.NS_OK, - Ci.calIOperationListener.MODIFY, - item.id, - item - ); + if (modifyCallback) { + await modifyCallback(item.calendar, Cr.NS_OK, Ci.calIOperationListener.MODIFY, item.id, item); } return item; } catch (e) { - if (this._cachedModifyItemCallback) { - await this._cachedModifyItemCallback( - this.superCalendar, - e.result || Cr.NS_ERROR_FAILURE, - Ci.calIOperationListener.MODIFY, - aNewItem.id, - aNewItem - ); + let code; + if (e instanceof ItemError) { + if (e.reason == ItemError.CONFLICT) { + let overwrite = cal.provider.promptOverwrite("modify", aOldItem); + if (overwrite) { + return this.modifyItem(aNewItem, aOldItem, { force: true }); + } else { + code = Ci.calIErrors.OPERATION_CANCELLED; + this.superCalendar.refresh(); + } + } else { + code = e.xpcomReason; + } + } else { + code = e.result || Cr.NS_ERROR_FAILURE; } - throw e; + throw new Components.Exception(e.message, code); } } - async deleteItem(aItem, aListener) { - let results = await this.extension.emit("calendar.provider.onItemRemoved", this, aItem); - if (!results.length) { - throw new Components.Exception( - "Extension did not consume item deletion", - Cr.NS_ERROR_FAILURE + async deleteItem(aItem, aOptions = {}) { + try { + let results = await this.extension.emit( + "calendar.provider.onItemRemoved", + this, + aItem, + aOptions ); - } - this.observers.notify("onDeleteItem", [aItem]); + if (!results.length) { + throw new Components.Exception( + "Extension did not consume item deletion", + Cr.NS_ERROR_FAILURE + ); + } + + // This will discover errors and throw them + this.discoverItem(results); + + this.observers.notify("onDeleteItem", [aItem]); + } catch (e) { + let code; + if (e instanceof ItemError) { + if (e.reason == ItemError.CONFLICT) { + let overwrite = cal.provider.promptOverwrite("delete", aItem); + if (overwrite) { + return this.deleteItem(aItem, { force: true }); + } else { + code = Ci.calIErrors.OPERATION_CANCELLED; + this.superCalendar.refresh(); + } + } else { + code = e.xpcomReason; + } + } else { + code = e.result || Cr.NS_ERROR_FAILURE; + } + + throw new Components.Exception(e.message, code); + } + return aItem; } getItem(aId) { @@ -386,15 +455,14 @@ this.calendar_provider = class extends ExtensionAPI { this.onManifestEntry("calendar_provider"); } - // TODO Change this for your add-on to avoid conflicts Services.io .getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) - .setSubstitution("experiment-calendar", this.extension.rootURI); + .setSubstitution("tb-experiments-calendar", this.extension.rootURI); - const { setupE10sBrowser } = ChromeUtils.import("resource://experiment-calendar/experiments/calendar/ext-calendar-utils.jsm"); + const { setupE10sBrowser } = ChromeUtils.importESModule("resource://tb-experiments-calendar/experiments/calendar/ext-calendar-utils.sys.mjs"); - ChromeUtils.registerWindowActor("CalendarProvider", { child: { moduleURI: "resource://experiment-calendar/experiments/calendar/child/ext-calendar-provider-actor.jsm" } }); + ChromeUtils.registerWindowActor("CalendarProvider", { child: { esModuleURI: "resource://tb-experiments-calendar/experiments/calendar/child/ext-calendar-provider-actor.sys.mjs" } }); ExtensionSupport.registerWindowListener("ext-calendar-provider-" + this.extension.id, { chromeURLs: ["chrome://calendar/content/calendar-creation.xhtml"], @@ -416,7 +484,9 @@ this.calendar_provider = class extends ExtensionAPI { } loadPromise.then(() => { - browser.loadURI(calendarType.panelSrc, { triggeringPrincipal: this.extension.principal }); + browser.fixupAndLoadURIString(calendarType.panelSrc, { + triggeringPrincipal: this.extension.principal + }); }); win.gButtonHandlers.forNodeId["panel-addon-calendar-settings"].accept = calendarType.onCreated; @@ -447,13 +517,11 @@ this.calendar_provider = class extends ExtensionAPI { ExtCalendarProvider.unregister(this.extension); } - Cu.unload("resource://experiment-calendar/experiments/calendar/ext-calendar-utils.jsm"); - + Cu.unload("resource://tb-experiments-calendar/experiments/calendar/ext-calendar-utils.sys.mjs"); Services.io .getProtocolHandler("resource") .QueryInterface(Ci.nsIResProtocolHandler) - .setSubstitution("experiment-calendar", null); - + .setSubstitution("tb-experiments-calendar", null); Services.obs.notifyObservers(null, "startupcache-invalidate", null); } @@ -485,7 +553,7 @@ this.calendar_provider = class extends ExtensionAPI { propsToItem, convertItem, convertCalendar, - } = ChromeUtils.import("resource://experiment-calendar/experiments/calendar/ext-calendar-utils.jsm"); + } = ChromeUtils.importESModule("resource://tb-experiments-calendar/experiments/calendar/ext-calendar-utils.sys.mjs"); return { calendar: { @@ -494,18 +562,24 @@ this.calendar_provider = class extends ExtensionAPI { context, name: "calendar.provider.onItemCreated", register: (fire, options) => { - let listener = async (event, calendar, item) => { + let listener = async (event, calendar, item, listenerOptions) => { let props = await fire.async( convertCalendar(context.extension, calendar), - convertItem(item, options, context.extension) + convertItem(item, options, context.extension), + listenerOptions ); - if (props?.type) { - item = propsToItem(props, item); - } - if (!item.id) { - item.id = cal.getUUID(); + + if (props?.error) { + return { error: props.error }; + } else { + if (props?.type) { + item = propsToItem(props, item); + } + if (!item.id) { + item.id = cal.getUUID(); + } + return { item, metadata: props?.metadata }; } - return { item, metadata: props?.metadata }; }; context.extension.on("calendar.provider.onItemCreated", listener); @@ -519,16 +593,21 @@ this.calendar_provider = class extends ExtensionAPI { context, name: "calendar.provider.onItemUpdated", register: (fire, options) => { - let listener = async (event, calendar, item, oldItem) => { + let listener = async (event, calendar, item, oldItem, listenerOptions) => { let props = await fire.async( convertCalendar(context.extension, calendar), convertItem(item, options, context.extension), - convertItem(oldItem, options, context.extension) + convertItem(oldItem, options, context.extension), + listenerOptions ); - if (props?.type) { - item = propsToItem(props, item); + if (props?.error) { + return { error: props.error }; + } else { + if (props?.type) { + item = propsToItem(props, item); + } + return { item, metadata: props?.metadata }; } - return { item, metadata: props?.metadata }; }; context.extension.on("calendar.provider.onItemUpdated", listener); @@ -542,11 +621,13 @@ this.calendar_provider = class extends ExtensionAPI { context, name: "calendar.provider.onItemRemoved", register: (fire, options) => { - let listener = (event, calendar, item) => { - return fire.async( + let listener = async (event, calendar, item, listenerOptions) => { + let res = await fire.async( convertCalendar(context.extension, calendar), - convertItem(item, options, context.extension) + convertItem(item, options, context.extension), + listenerOptions ); + return res; }; context.extension.on("calendar.provider.onItemRemoved", listener); diff --git a/calendar/experiments/calendar/parent/ext-calendarItemAction.js b/calendar/experiments/calendar/parent/ext-calendarItemAction.js index c2acc58..28ccec4 100644 --- a/calendar/experiments/calendar/parent/ext-calendarItemAction.js +++ b/calendar/experiments/calendar/parent/ext-calendarItemAction.js @@ -2,29 +2,11 @@ * 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/. */ -ChromeUtils.defineModuleGetter( - this, - "ToolbarButtonAPI", - "resource:///modules/ExtensionToolbarButtons.jsm" -); -ChromeUtils.defineModuleGetter( - this, - "ExtensionParent", - "resource://gre/modules/ExtensionParent.jsm" -); - -ChromeUtils.defineModuleGetter( - this, - "ExtensionSupport", - "resource:///modules/ExtensionSupport.jsm", -); - - -var { ExtensionCommon } = ChromeUtils.import( - "resource://gre/modules/ExtensionCommon.jsm" -); - -var { makeWidgetId } = ExtensionCommon; +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(); diff --git a/calendar/experiments/calendar/parent/ext-calendarItemDetails.js b/calendar/experiments/calendar/parent/ext-calendarItemDetails.js index c87e25f..f2e039a 100644 --- a/calendar/experiments/calendar/parent/ext-calendarItemDetails.js +++ b/calendar/experiments/calendar/parent/ext-calendarItemDetails.js @@ -2,115 +2,27 @@ * 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/. */ -ChromeUtils.defineModuleGetter( - this, - "ExtensionSupport", - "resource:///modules/ExtensionSupport.jsm", -); - -var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm"); -var { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); -var { ExtensionUtils } = ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm"); - -var { promiseEvent } = ExtensionUtils; -var { makeWidgetId, ExtensionAPI } = ExtensionCommon; - - -XPCOMUtils.defineLazyGetter(this, "standaloneStylesheets", () => { - let stylesheets = []; - let { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); - - if (AppConstants.platform === "macosx") { - stylesheets.push("chrome://browser/content/extension-mac-panel.css"); - } else if (AppConstants.platform === "win") { - stylesheets.push("chrome://browser/content/extension-win-panel.css"); - } else if (AppConstants.platform === "linux") { - stylesheets.push("chrome://browser/content/extension-linux-panel.css"); - } - return stylesheets; -}); +var { + ExtensionCommon: { ExtensionAPI, makeWidgetId } +} = ChromeUtils.importESModule("resource://gre/modules/ExtensionCommon.sys.mjs"); +var { ExtensionSupport } = ChromeUtils.importESModule("resource:///modules/ExtensionSupport.sys.mjs"); this.calendarItemDetails = class extends ExtensionAPI { - async _attachBrowser(tabpanel) { - let document = tabpanel.ownerDocument; - let browser = document.createXULElement("browser"); - browser.setAttribute("flex", "1"); - browser.setAttribute("type", "content"); - browser.setAttribute("disableglobalhistory", "true"); - browser.setAttribute("messagemanagergroup", "webext-browsers"); - browser.setAttribute("transparent", "true"); - browser.setAttribute("class", "webextension-popup-browser"); - browser.setAttribute("webextension-view-type", "subview"); - - // Ensure the browser will initially load in the same group as other browsers from the same - // extension. - browser.setAttribute( - "initialBrowsingContextGroupId", - this.extension.policy.browsingContextGroupId - ); - - if (this.extension.remote) { - browser.setAttribute("remote", "true"); - browser.setAttribute("remoteType", this.extension.remoteType); - browser.setAttribute("maychangeremoteness", "true"); - } - - let readyPromise; - if (this.extension.remote) { - readyPromise = promiseEvent(browser, "XULFrameLoaderCreated"); - } else { - readyPromise = promiseEvent(browser, "load"); - } - - tabpanel.appendChild(browser); - - if (!this.extension.remote) { - // FIXME: bug 1494029 - this code used to rely on the browser binding - // accessing browser.contentWindow. This is a stopgap to continue doing - // that, but we should get rid of it in the long term. - browser.contentwindow; // eslint-disable-line no-unused-expressions - } - - let sheets = []; - if (this.extension.manifest.calendar_item_details.browser_style) { - sheets.push(...ExtensionParent.extensionStylesheets); - } - sheets.push(...standaloneStylesheets); - - - const initBrowser = () => { - ExtensionParent.apiManager.emit("extension-browser-inserted", browser); - let mm = browser.messageManager; - mm.loadFrameScript( - "chrome://extensions/content/ext-browser-content.js", - false, - true - ); - - mm.sendAsyncMessage("Extension:InitBrowser", { - allowScriptsToClose: true, - blockParser: false, - maxWidth: 800, - maxHeight: 600, - stylesheets: sheets - }); - }; - browser.addEventListener("DidChangeBrowserRemoteness", initBrowser); - - return readyPromise.then(() => { - initBrowser(); - browser.loadURI(this.extension.manifest.calendar_item_details.default_content, { triggeringPrincipal: this.extension.principal }); - }); - } - onLoadCalendarItemPanel(window, origLoadCalendarItemPanel, iframeId, url) { + const { setupE10sBrowser } = ChromeUtils.importESModule("resource://tb-experiments-calendar/experiments/calendar/ext-calendar-utils.sys.mjs"); + let res = origLoadCalendarItemPanel(iframeId, url); if (this.extension.manifest.calendar_item_details) { - let panelFrame = window.document.getElementById(iframeId || "calendar-item-panel-iframe"); + let panelFrame; + if (window.tabmail) { + panelFrame = window.document.getElementById(iframeId|| tabmail.currentTabInfo.iframe?.id); + } else { + panelFrame = window.document.getElementById("calendar-item-panel-iframe"); + } + panelFrame.contentWindow.addEventListener("load", (event) => { let document = event.target.ownerGlobal.document; - console.log(this.extension.manifest.calendar_item_details); let widgetId = makeWidgetId(this.extension.id); @@ -128,7 +40,15 @@ this.calendarItemDetails = class extends ExtensionAPI { tabpanel.setAttribute("id", widgetId + "-calendarItemDetails-tabpanel"); tabpanel.setAttribute("flex", "1"); - this._attachBrowser(tabpanel); + let browser = document.createXULElement("browser"); + browser.setAttribute("flex", "1"); + let loadPromise = setupE10sBrowser(this.extension, browser, tabpanel); + + return loadPromise.then(() => { + browser.fixupAndLoadURIString(this.extension.manifest.calendar_item_details.default_content, { + triggeringPrincipal: this.extension.principal + }); + }); }); } @@ -158,13 +78,29 @@ this.calendarItemDetails = class extends ExtensionAPI { "chrome://calendar/content/calendar-event-dialog.xhtml" ], onLoadWindow: (window) => { - let orig = window.onLoadCalendarItemPanel; - window.onLoadCalendarItemPanel = this.onLoadCalendarItemPanel.bind(this, window, orig.bind(window)); + if (window.location.href == "chrome://messenger/content/messenger.xhtml") { + let orig = window.onLoadCalendarItemPanel; + window.onLoadCalendarItemPanel = this.onLoadCalendarItemPanel.bind(this, window, orig.bind(window)); + window._onLoadCalendarItemPanelOrig = orig; + } else { + window.setTimeout(() => { + this.onLoadCalendarItemPanel(window, () => {}); + }, 0); + } } }); } onShutdown() { ExtensionSupport.unregisterWindowListener("ext-calendarItemDetails-" + this.extension.id); + + for (let wnd of ExtensionSupport.openWindows) { + if (wnd.location.href == "chrome://messenger/content/messenger.xhtml") { + if (wnd._onLoadCalendarItemPanelOrig) { + wnd.onLoadCalendarItemPanel = wnd._onLoadCalendarItemPanelOrig; + wnd._onLoadCalendarItemPanelOrig = null; + } + } + } } getAPI(context) { return { calendar: { itemDetails: {} } }; diff --git a/calendar/experiments/calendar/schema/calendar-calendars.json b/calendar/experiments/calendar/schema/calendar-calendars.json index 28bd10e..7b81444 100644 --- a/calendar/experiments/calendar/schema/calendar-calendars.json +++ b/calendar/experiments/calendar/schema/calendar-calendars.json @@ -151,7 +151,8 @@ "readOnly": { "type": "boolean", "optional": true }, "enabled": { "type": "boolean", "optional": true }, "color": { "type": "string", "optional": true }, - "capabilities": { "$ref": "CalendarCapabilities", "optional": true } + "capabilities": { "$ref": "CalendarCapabilities", "optional": true }, + "lastError": { "type": "string", "optional": true } } } ] diff --git a/calendar/experiments/calendar/schema/calendar-provider.json b/calendar/experiments/calendar/schema/calendar-provider.json index 3ff305a..36abeab 100644 --- a/calendar/experiments/calendar/schema/calendar-provider.json +++ b/calendar/experiments/calendar/schema/calendar-provider.json @@ -22,13 +22,41 @@ }, { "namespace": "calendar.provider", + "types": [{ + "id": "ItemError", + "type": "object", + "description": "A failure in an onItem* handler", + "properties": { + "error": { + "type": "string", + "enum": [ + "GENERAL_FAILURE", + "READ_FAILED", + "MODIFY_FAILED", + "CONFLICT" + ] + } + } + }, { + "id": "ItemOptions", + "type": "object", + "description": "Options for the create/modify/delete event handlers", + "properties": { + "force": { + "type": "boolean", + "description": "If true, instruct the provider to force overwrite changes (i.e. after a conflict)", + "optional": true + } + } + }], "events": [ { "name": "onItemCreated", "type": "function", "parameters": [ { "name": "calendar", "$ref": "calendar.calendars.Calendar" }, - { "name": "item", "$ref": "calendar.items.CalendarItem" } + { "name": "item", "$ref": "calendar.items.CalendarItem" }, + { "name": "options", "$ref": "calendar.provider.ItemOptions" } ], "extraParameters": [ { @@ -38,7 +66,14 @@ "returnFormat": { "$ref": "calendar.items.ReturnFormat", "optional": true } } } - ] + ], + "returns": { + "description": "Returns the added item, or an error that occurred", + "choices": [ + { "$ref": "calendar.provider.ItemError" }, + { "$ref": "calendar.items.CalendarItem" } + ] + } }, { "name": "onItemUpdated", @@ -46,7 +81,8 @@ "parameters": [ { "name": "calendar", "$ref": "calendar.calendars.Calendar" }, { "name": "item", "$ref": "calendar.items.CalendarItem" }, - { "name": "oldItem", "$ref": "calendar.items.CalendarItem" } + { "name": "oldItem", "$ref": "calendar.items.CalendarItem" }, + { "name": "options", "$ref": "calendar.provider.ItemOptions" } ], "extraParameters": [ { @@ -56,14 +92,22 @@ "returnFormat": { "$ref": "calendar.items.ReturnFormat", "optional": true } } } - ] + ], + "returns": { + "description": "Returns the modified item, or an error that occurred", + "choices": [ + { "$ref": "calendar.provider.ItemError" }, + { "$ref": "calendar.items.CalendarItem" } + ] + } }, { "name": "onItemRemoved", "type": "function", "parameters": [ { "name": "calendar", "$ref": "calendar.calendars.Calendar" }, - { "name": "item", "$ref": "calendar.items.CalendarItem" } + { "name": "item", "$ref": "calendar.items.CalendarItem" }, + { "name": "options", "$ref": "calendar.provider.ItemOptions" } ], "extraParameters": [ { @@ -73,7 +117,12 @@ "returnFormat": { "$ref": "calendar.items.ReturnFormat", "optional": true } } } - ] + ], + "returns": { + "description": "Optionally returns an item error if it occurred", + "optional": true, + "$ref": "calendar.provider.ItemError" + } }, { "name": "onInit", diff --git a/calendar/experiments/calendar/schema/calendarItemAction.json b/calendar/experiments/calendar/schema/calendarItemAction.json index 2288b0a..093e384 100644 --- a/calendar/experiments/calendar/schema/calendarItemAction.json +++ b/calendar/experiments/calendar/schema/calendarItemAction.json @@ -50,6 +50,13 @@ "description": "Currently unused.", "type": "string", "optional": true + }, + "type": { + "description": "Specifies the type of the button. Default type is button.", + "type": "string", + "enum": ["button", "menu"], + "optional": true, + "default": "button" } }, "optional": true diff --git a/calendar/manifest.json b/calendar/manifest.json index 513362e..d1c458e 100644 --- a/calendar/manifest.json +++ b/calendar/manifest.json @@ -4,7 +4,7 @@ "author": "Philipp Kewisch", "description": "Draft experiment for Thunderbird MailExtensions covering calendaring", "version": "2.2.0", - "applications": { + "browser_specific_settings": { "gecko": { "id": "ext-calendar-draft@mozilla.kewis.ch" } @@ -44,7 +44,8 @@ "calendar_item_action": { "default_icon": "addon.png", "default_popup": "content/calendar-item-action.html", - "default_title": "Calendar Item Action" + "default_title": "Calendar Item Action", + "type": "button" }, "background": {