diff --git a/calendar/experiments/calendar/parent/ext-calendar-calendars.js b/calendar/experiments/calendar/parent/ext-calendar-calendars.js index c260de4..d47a3a4 100644 --- a/calendar/experiments/calendar/parent/ext-calendar-calendars.js +++ b/calendar/experiments/calendar/parent/ext-calendar-calendars.js @@ -92,15 +92,29 @@ this.calendar_calendars = class extends ExtensionAPI { } calendar.name = createProperties.name; - if (typeof createProperties.color != "undefined") { + + if (createProperties.color != null) { calendar.setProperty("color", createProperties.color); } - if (typeof createProperties.visible != "undefined") { + if (createProperties.readOnly != null) { + calendar.setProperty("readOnly", createProperties.readOnly); + } + if (createProperties.enabled != null) { + calendar.setProperty("disabled", !createProperties.enabled); + } + if (createProperties.visible != null) { calendar.setProperty("calendar-main-in-composite", createProperties.visible); } - if (typeof createProperties.showReminders != "undefined") { + if (createProperties.showReminders != null) { calendar.setProperty("suppressAlarms", !createProperties.showReminders); } + if (createProperties.capabilities != null) { + if (!isOwnCalendar(calendar, context.extension)) { + throw new ExtensionError("Cannot set capabilities on foreign calendar types"); + } + + calendar.setProperty("overrideCapabilities", JSON.stringify(createProperties.capabilities)); + } cal.manager.registerCalendar(calendar); @@ -145,17 +159,30 @@ this.calendar_calendars = class extends ExtensionAPI { if (updateProperties.capabilities) { // TODO validate capability names const unwrappedCalendar = calendar.wrappedJSObject.mUncachedCalendar.wrappedJSObject; - unwrappedCalendar.capabilities = Object.assign({}, unwrappedCalendar.capabilities, updateProperties.capabilities); - calendar.setProperty("extensionCapabilities", JSON.stringify(unwrappedCalendar.capabilities)); + let overrideCapabilities; + try { + overrideCapabilities = JSON.parse(calendar.getProperty("overrideCapabilities")) || {}; + } catch(e) { + overrideCapabilities = {}; + } + for (const [key, value] of Object.entries(updateProperties.capabilities)) { + if (value === null) { + continue; + } + unwrappedCalendar.capabilities[key] = value; + overrideCapabilities[key] = value; + } + + calendar.setProperty("overrideCapabilities", JSON.stringify(overrideCapabilities)); } 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", ""); + } else { + calendar.setProperty("currentStatus", Cr.NS_ERROR_FAILURE); + calendar.setProperty("lastErrorMessage", updateProperties.lastError); } } }, diff --git a/calendar/experiments/calendar/parent/ext-calendar-provider.js b/calendar/experiments/calendar/parent/ext-calendar-provider.js index 9dd2f22..f06efd6 100644 --- a/calendar/experiments/calendar/parent/ext-calendar-provider.js +++ b/calendar/experiments/calendar/parent/ext-calendar-provider.js @@ -8,6 +8,22 @@ var { ExtensionUtils: { ExtensionError } } = ChromeUtils.importESModule("resourc var { cal } = ChromeUtils.importESModule("resource:///modules/calendar/calUtils.sys.mjs"); var { ExtensionSupport } = ChromeUtils.importESModule("resource:///modules/ExtensionSupport.sys.mjs"); +var { CalItipEmailTransport } = ChromeUtils.importESModule("resource:///modules/CalItipEmailTransport.sys.mjs"); + +// TODO move me +// Have the server take care of scheduling. This can be de-duplicated in +// CalItipEmailTransport.sys.mjs +class CalItipNoEmailTransport extends CalItipEmailTransport { + wrappedJSObject = this; + QueryInterface = ChromeUtils.generateQI(["calIItipTransport"]); + + sendItems() { + return true; + } +} + + + // TODO move me function getNewCalendarWindow() { // This window is missing a windowtype attribute @@ -143,13 +159,15 @@ class ExtCalendar extends cal.provider.BaseClass { set id(val) { super.id = val; if (this.id && this.uri) { + let overrideCapabilities; try { - this.capabilities = JSON.parse(super.getProperty("extensionCapabilities")); + overrideCapabilities = JSON.parse(super.getProperty("overrideCapabilities")) || {}; } catch (e) { - this.capabilities = null; + overrideCapabilities = {}; } - this.capabilities ??= this.extension.manifest.calendar_provider.capabilities || {}; + const manifestCapabilities = this.extension.manifest.calendar_provider.capabilities || {}; + this.capabilities = Object.assign({}, manifestCapabilities, overrideCapabilities); this.extension.emit("calendar.provider.onInit", this); } @@ -164,6 +182,14 @@ class ExtCalendar extends cal.provider.BaseClass { } } + get supportsScheduling() { + return this.capabilities.scheduling != "none"; + } + + getSchedulingSupport() { + return this; + } + setProperty(name, value) { if (name === "readOnly" && this.capabilities.mutable === false) { return; // prevent change @@ -188,6 +214,16 @@ class ExtCalendar extends cal.provider.BaseClass { return this.capabilities.organizerName; } break; + case "imip.identity.disabled": + return this.capabilities.scheduling == "none"; + case "itip.transport": + if (this.capabilities.scheduling == "server") { + return new CalItipNoEmailTransport(); + } else if (this.capabilities.scheduling == "none") { + return null; + } + // Else fall through and have super return the client email transport + break; case "readOnly": if (this.capabilities.mutable === false) { @@ -225,11 +261,11 @@ class ExtCalendar extends cal.provider.BaseClass { case "capabilities.events.supported": return !(this.capabilities.events === false); case "capabilities.removeModes": - return Array.isArray(this.capabilities.remove_modes) - ? this.capabilities.remove_modes + return Array.isArray(this.capabilities.removeModes) + ? this.capabilities.removeModes : ["unsubscribe"]; case "requiresNetwork": - return !(this.capabilities.requires_network === false); + return !(this.capabilities.requiresNetwork === false); } return super.getProperty(name); @@ -278,7 +314,7 @@ class ExtCalendar extends cal.provider.BaseClass { return item; } catch (e) { let code; - if (e.message.startsWith("NetworkError")) { + if (e.message?.startsWith("NetworkError")) { code = Cr.NS_ERROR_NET_INTERRUPT; } else if (e instanceof ItemError) { code = e.xpcomReason; @@ -286,7 +322,7 @@ class ExtCalendar extends cal.provider.BaseClass { code = e.result || Cr.NS_ERROR_FAILURE; } - throw new Components.Exception(e.message, code); + throw new Components.Exception(e.message || e, code); } } @@ -354,7 +390,7 @@ class ExtCalendar extends cal.provider.BaseClass { return item; } catch (e) { let code; - if (e.message.startsWith("NetworkError")) { + if (e.message?.startsWith("NetworkError")) { code = Cr.NS_ERROR_NET_INTERRUPT; } else if (e instanceof ItemError) { if (e.reason == ItemError.CONFLICT) { @@ -370,7 +406,7 @@ class ExtCalendar extends cal.provider.BaseClass { } else { code = e.result || Cr.NS_ERROR_FAILURE; } - throw new Components.Exception(e.message, code); + throw new Components.Exception(e.message || e, code); } } @@ -403,7 +439,7 @@ class ExtCalendar extends cal.provider.BaseClass { this.observers.notify("onDeleteItem", [aItem]); } catch (e) { let code; - if (e.message.startsWith("NetworkError")) { + if (e.message?.startsWith("NetworkError")) { code = Cr.NS_ERROR_NET_INTERRUPT; } else if (e instanceof ItemError) { if (e.reason == ItemError.CONFLICT) { @@ -420,7 +456,7 @@ class ExtCalendar extends cal.provider.BaseClass { code = e.result || Cr.NS_ERROR_FAILURE; } - throw new Components.Exception(e.message, code); + throw new Components.Exception(e.message || e, code); } return aItem; } @@ -447,11 +483,17 @@ class ExtCalendar extends cal.provider.BaseClass { async replayChangesOn(aListener) { this.offlineStorage.startBatch(); try { - await this.extension.emit("calendar.provider.onSync", this); - aListener.onResult({ status: Cr.NS_OK }, null); - } catch (e) { - console.error(e); - aListener.onResult({ status: e.result || Cr.NS_ERROR_FAILURE }, e.message || e); + let status = Cr.NS_OK + let detail = null; + try { + await this.extension.emit("calendar.provider.onSync", this); + } catch (e) { + status = e.result || Cr.NS_ERROR_FAILURE; + detail = e.message || e; + console.error(e); + } + + aListener.onResult({ status }, detail); } finally { this.offlineStorage.endBatch(); } @@ -527,7 +569,7 @@ this.calendar_provider = class extends ExtensionAPI { win.gIdentityNotification.removeAllNotifications(); } - const minRefresh = calendar.capabilities?.minimumRefresh; + const minRefresh = calendar.capabilities?.minimumRefreshInterval; if (minRefresh) { const refInterval = win.document.getElementById("calendar-refreshInterval-menupopup"); @@ -607,6 +649,22 @@ this.calendar_provider = class extends ExtensionAPI { win.gAddonAdvance = new EventEmitter(); } + + const origCheckRequired = win.checkRequired; + win.checkRequired = () => { + origCheckRequired(); + const addonPanel = win.document.getElementById("panel-addon-calendar-settings"); + if (addonPanel.hidden) { + return; + } + + const dialog = win.document.getElementById("calendar-creation-dialog"); + if (addonPanel.dataset.addonNoForward == "true") { + dialog.setAttribute("buttondisabledaccept", "true"); + } else { + dialog.removeAttribute("buttondisabledaccept"); + } + }; } }); } @@ -821,7 +879,7 @@ this.calendar_provider = class extends ExtensionAPI { // New calendar dialog - async setAdvanceAction({ forward, back, label }) { + async setAdvanceAction({ forward, back, label, canForward }) { const window = getNewCalendarWindow(); if (!window) { throw new ExtensionError("New calendar wizard is not open"); @@ -843,6 +901,11 @@ this.calendar_provider = class extends ExtensionAPI { if (!addonPanel.hidden) { window.updateButton("accept", addonPanel); } + + if (typeof canForward === "boolean") { + addonPanel.dataset.addonNoForward = !canForward + window.checkRequired(); + } }, onAdvanceNewCalendar: new EventManager({ context, diff --git a/calendar/experiments/calendar/parent/ext-calendarItemDetails.js b/calendar/experiments/calendar/parent/ext-calendarItemDetails.js index 1e9d58e..29ddefc 100644 --- a/calendar/experiments/calendar/parent/ext-calendarItemDetails.js +++ b/calendar/experiments/calendar/parent/ext-calendarItemDetails.js @@ -33,9 +33,12 @@ this.calendarItemDetails = class extends ExtensionAPI { panelFrame.contentWindow.addEventListener("load", (event) => { const document = event.target.ownerGlobal.document; - let areas = this.extension.manifest.calendar_item_details.allowed_areas || ["secondary"]; - if (!Array.isArray(areas)) { - areas = [areas]; + let areas = []; + if (this.extension.manifest.calendar_item_details) { + areas = this.extension.manifest.calendar_item_details.allowed_areas || ["secondary"] + if (!Array.isArray(areas)) { + areas = [areas]; + } } if (areas.includes("secondary")) { @@ -77,23 +80,32 @@ this.calendarItemDetails = class extends ExtensionAPI { const separatorCell = separator.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "td")); separatorCell.setAttribute("colspan", "2"); + // Fix an annoying bug, this should be part of a different patch + document.getElementById("url-link").style.maxWidth = "42em"; + const browser = document.createXULElement("browser"); browser.setAttribute("flex", "1"); - // TODO The real version will need a max-height and auto-resizing - browser.style.height = "200px"; browser.style.width = "100%"; browser.style.display = "block"; - // Fix an annoying bug, this should be part of a different patch - document.getElementById("url-link").style.maxWidth = "42em"; - - const options = { maxWidth: null, fixedWidth: true }; - setupE10sBrowser(this.extension, browser, browserCell, options).then(() => { - const target = new URL(this.extension.manifest.calendar_item_details.default_content); - target.searchParams.set("area", "inline"); - browser.fixupAndLoadURIString(target.href, { triggeringPrincipal: this.extension.principal }); - }); + // Do this on the next tick so the column width is correctly calculated + window.setTimeout(() => { + const columnWidth = document.querySelector("#event-grid th")?.offsetWidth || 0; + + const options = { maxWidth: null, fixedWidth: true, maxHeight: 200 }; + setupE10sBrowser(this.extension, browser, browserCell, options).then(() => { + browser.messageManager.addMessageListener("Extension:BrowserResized", function(message) { + if (message.data.detail == "delayed") { + browser.style.height = `${message.data.height}px`; + } + }); + const target = new URL(this.extension.manifest.calendar_item_details.default_content); + target.searchParams.set("area", "inline"); + target.searchParams.set("columnWidth", columnWidth); + browser.fixupAndLoadURIString(target.href, { triggeringPrincipal: this.extension.principal }); + }); + }, 0); } }); @@ -113,12 +125,14 @@ this.calendarItemDetails = class extends ExtensionAPI { // Fix an annoying bug, this should be part of a different patch document.querySelector(".url-link").style.maxWidth = "42em"; - let areas = this.extension.manifest.calendar_item_details.allowed_areas || ["secondary"]; - if (!Array.isArray(areas)) { - areas = [areas]; + let areas = []; + if (this.extension.manifest.calendar_item_details) { + areas = this.extension.manifest.calendar_item_details.allowed_areas || ["secondary"] + if (!Array.isArray(areas)) { + areas = [areas]; + } } - if (areas.includes("summary")) { const summaryBox = document.querySelector(".item-summary-box"); diff --git a/calendar/experiments/calendar/schema/calendar-calendars.json b/calendar/experiments/calendar/schema/calendar-calendars.json index 388fae5..abc4a6c 100644 --- a/calendar/experiments/calendar/schema/calendar-calendars.json +++ b/calendar/experiments/calendar/schema/calendar-calendars.json @@ -72,7 +72,7 @@ }, "tasks": { "type": "boolean", "optional": true, "default": true }, "events": { "type": "boolean", "optional": true, "default": true }, - "remove_modes": { + "removeModes": { "type": "array", "optional": true, "default": ["unsubscribe"], @@ -81,9 +81,15 @@ "enum": ["unsubscribe", "delete"] } }, - "requires_network": { "type": "boolean", "optional": true }, - "minimum_refresh_interval": { "type": "integer", "minimum": -1, "optional": true }, - "mutable": { "type": "boolean", "optional": true, "default": true } + "requiresNetwork": { "type": "boolean", "optional": true }, + "minimumRefreshInterval": { "type": "integer", "minimum": -1, "optional": true }, + "mutable": { "type": "boolean", "optional": true, "default": true }, + "scheduling": { + "type": "string", + "optional": true, + "default": "client", + "enum": ["client", "server", "none"] + } } } ], @@ -139,7 +145,12 @@ "visible": { "type": "boolean", "optional": true }, "showReminders": { "type": "boolean", "optional": true }, "color": { "type": "string", "optional": true }, - "capabilities": { "$ref": "CalendarCapabilities", "optional": true } + "capabilities": { + "type": "object", + "optional": true, + "additionalProperties": true, + "description": "This is normally CalendarCapabilities, but we don't want to inherit default values here" + } } } ] @@ -159,8 +170,13 @@ "readOnly": { "type": "boolean", "optional": true }, "enabled": { "type": "boolean", "optional": true }, "color": { "type": "string", "optional": true }, - "capabilities": { "$ref": "CalendarCapabilities", "optional": true }, - "lastError": { "type": "string", "optional": true } + "lastError": { "type": "string", "optional": true }, + "capabilities": { + "type": "object", + "optional": true, + "additionalProperties": true, + "description": "This is normally CalendarCapabilities, but we don't want to inherit default values here" + } } } ] diff --git a/calendar/experiments/calendar/schema/calendar-provider.json b/calendar/experiments/calendar/schema/calendar-provider.json index ce3a4fd..91d95b5 100644 --- a/calendar/experiments/calendar/schema/calendar-provider.json +++ b/calendar/experiments/calendar/schema/calendar-provider.json @@ -68,9 +68,10 @@ { "type": "object", "properties": { - "forward": { "type": "string" }, + "forward": { "type": "string", "optional": true }, "back": { "type": "string", "optional": true }, - "label": { "type": "string" } + "label": { "type": "string", "optional": true }, + "canForward": { "type": "boolean", "optional": true } } } ]