Bug 1561528 - Use CSS variables for calendar colours when printing rather than dynamically adding CSS rules. r=philipp
authorGeoff Lankow <geoff@darktrojan.net>
Wed, 26 Jun 2019 20:23:05 +1200
changeset 35999 b02d7fcf98333e52269e6f83f9c03f65b0770505
parent 35998 796d020bab06cde6baf0882a39f90d2560ebae2a
child 36000 0a4f2477de1d93053d5ffefb89b32a58a600412e
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersphilipp
bugs1561528
Bug 1561528 - Use CSS variables for calendar colours when printing rather than dynamically adding CSS rules. r=philipp
calendar/base/modules/utils/calPrintUtils.jsm
calendar/base/modules/utils/calViewUtils.jsm
calendar/import-export/calMonthGridPrinter.js
calendar/import-export/calWeekPrinter.js
--- a/calendar/base/modules/utils/calPrintUtils.jsm
+++ b/calendar/base/modules/utils/calPrintUtils.jsm
@@ -1,13 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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 { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "cal", "resource://calendar/modules/calUtils.jsm", "cal");
 
 /*
  * Helpers for printing and print preparation
  */
 
@@ -24,65 +23,16 @@ var calprint = {
      * @param dt    The date to translate
      * @return      YYYY-MM-DD
      */
     getDateKey: function(date) {
         return date.year + "-" + date.month + "-" + date.day;
     },
 
     /**
-     * Add category styles to the document's "sheet" element. This is needed
-     * since the HTML created is serialized, so we can't dynamically set the
-     * styles and can be changed if the print formatter decides to return a
-     * DOM document instead.
-     *
-     * @param document      The document that contains <style id="sheet"/>.
-     * @param categories    Array of categories to insert rules for.
-     */
-    insertCategoryRules: function(document, categories) {
-        let sheet = document.getElementById("sheet");
-        sheet.insertedCategoryRules = sheet.insertedCategoryRules || {};
-
-        for (let category of categories) {
-            let prefName = cal.view.formatStringForCSSRule(category);
-            let color = Services.prefs.getStringPref("calendar.category.color." + prefName, "transparent");
-            if (!(prefName in sheet.insertedCategoryRules)) {
-                sheet.insertedCategoryRules[prefName] = true;
-                let ruleAdd = ' .category-color-box[categories~="' + prefName + '"] { ' +
-                              " border: 2px solid " + color + "; }\n";
-                sheet.textContent += ruleAdd;
-            }
-        }
-    },
-
-    /**
-     * Add calendar styles to the document's "sheet" element. This is needed
-     * since the HTML created is serialized, so we can't dynamically set the
-     * styles and can be changed if the print formatter decides to return a
-     * DOM document instead.
-     *
-     * @param document      The document that contains <style id="sheet"/>.
-     * @param categories    The calendar to insert a rule for.
-     */
-    insertCalendarRules: function(document, calendar) {
-        let sheet = document.getElementById("sheet");
-        let color = calendar.getProperty("color") || "#A8C2E1";
-        sheet.insertedCalendarRules = sheet.insertedCalendarRules || {};
-
-        if (!(calendar.id in sheet.insertedCalendarRules)) {
-            sheet.insertedCalendarRules[calendar.id] = true;
-            let formattedId = cal.view.formatStringForCSSRule(calendar.id);
-            let ruleAdd = ' .calendar-color-box[calendar-id="' + formattedId + '"] { ' +
-                          " background-color: " + color + "; " +
-                          " color: " + cal.view.getContrastingTextColor(color) + "; }\n";
-            sheet.textContent += ruleAdd;
-        }
-    },
-
-    /**
      * Serializes the given item by setting marked nodes to the item's content.
      * Has some expectations about the DOM document (in CSS-selector-speak), all
      * following nodes MUST exist.
      *
      * - #item-template will be cloned and filled, and modified:
      *   - .item-interval gets the time interval of the item.
      *   - .item-title gets the item title
      *   - .category-color-box gets a 2px solid border in category color
@@ -102,26 +52,26 @@ var calprint = {
         let itemInterval = cal.print.getItemIntervalString(item, boxDate);
         itemNode.querySelector(".item-interval").textContent = itemInterval;
         itemNode.querySelector(".item-title").textContent = item.title;
 
         // Fill in category details
         let categoriesArray = item.getCategories({});
         if (categoriesArray.length > 0) {
             let cssClassesArray = categoriesArray.map(cal.view.formatStringForCSSRule);
-            itemNode.querySelector(".category-color-box")
-                    .setAttribute("categories", cssClassesArray.join(" "));
-
-            cal.print.insertCategoryRules(document, categoriesArray);
+            let categoriesBox = itemNode.querySelector(".category-color-box");
+            categoriesBox.setAttribute("categories", cssClassesArray.join(" "));
+            categoriesBox.style.border = `2px solid var(--category-${cssClassesArray[0]}-color)`;
         }
 
         // Fill in calendar color
-        itemNode.querySelector(".calendar-color-box")
-                .setAttribute("calendar-id", cal.view.formatStringForCSSRule(item.calendar.id));
-        cal.print.insertCalendarRules(document, item.calendar);
+        let cssSafeId = cal.view.formatStringForCSSRule(item.calendar.id);
+        let colorBox = itemNode.querySelector(".calendar-color-box");
+        colorBox.style.color = `var(--calendar-${cssSafeId}-forecolor)`;
+        colorBox.style.backgroundColor = `var(--calendar-${cssSafeId}-backcolor)`;
 
         // Add it to the day container in the right order
         cal.data.binaryInsertNode(dayContainer, itemNode, item, cal.view.compareItems);
     },
 
     /**
      * Serializes the given item by setting marked nodes to the item's
      * content. Should be used for tasks with no start and due date. Has
--- a/calendar/base/modules/utils/calViewUtils.jsm
+++ b/calendar/base/modules/utils/calViewUtils.jsm
@@ -297,102 +297,106 @@ calview.colorTracker = {
     calendars: null,
     categoryBranch: null,
     windows: new Set(),
     QueryInterface: cal.generateQI([
         Ci.calICalendarManagerObserver,
         Ci.calIObserver
     ]),
 
-    // The only public method. Deregistration is not required.
+    // Deregistration is not required.
     registerWindow(aWindow) {
         if (this.calendars === null) {
             let manager = cal.getCalendarManager();
             this.calendars = new Set(manager.getCalendars({}));
             manager.addObserver(this);
             manager.addCalendarObserver(this);
             this.categoryBranch = Services.prefs.getBranch("calendar.category.color.");
             this.categoryBranch.addObserver("", this);
             Services.obs.addObserver(this, "xpcom-shutdown");
         }
 
         this.windows.add(aWindow);
         aWindow.addEventListener("unload", () => this.windows.delete(aWindow));
+        this.addColorsToDocument(aWindow.document);
+    },
+    addColorsToDocument(aDocument) {
         for (let calendar of this.calendars) {
-            this._addCalendarToWindow(aWindow, calendar);
+            this._addCalendarToDocument(aDocument, calendar);
         }
-        this._addAllCategoriesToWindow(aWindow);
+        this._addAllCategoriesToDocument(aDocument);
     },
-    _addCalendarToWindow(aWindow, aCalendar) {
+
+    _addCalendarToDocument(aDocument, aCalendar) {
         let cssSafeId = calview.formatStringForCSSRule(aCalendar.id);
-        let style = aWindow.document.documentElement.style;
+        let style = aDocument.documentElement.style;
         let backColor = aCalendar.getProperty("color") || "#a8c2e1";
         let foreColor = calview.getContrastingTextColor(backColor);
         style.setProperty(`--calendar-${cssSafeId}-backcolor`, backColor);
         style.setProperty(`--calendar-${cssSafeId}-forecolor`, foreColor);
     },
-    _removeCalendarFromWindow(aWindow, aCalendar) {
+    _removeCalendarFromDocument(aDocument, aCalendar) {
         let cssSafeId = calview.formatStringForCSSRule(aCalendar.id);
-        let style = aWindow.document.documentElement.style;
+        let style = aDocument.documentElement.style;
         style.removeProperty(`--calendar-${cssSafeId}-backcolor`);
         style.removeProperty(`--calendar-${cssSafeId}-forecolor`);
     },
-    _addCategoryToWindow(aWindow, aCategoryName) {
+    _addCategoryToDocument(aDocument, aCategoryName) {
         if (/[^\w-]/.test(aCategoryName)) {
             return;
         }
 
         let cssSafeName = calview.formatStringForCSSRule(aCategoryName);
-        let style = aWindow.document.documentElement.style;
+        let style = aDocument.documentElement.style;
         let color = this.categoryBranch.getStringPref(aCategoryName, "transparent");
         style.setProperty(`--category-${cssSafeName}-color`, color);
     },
-    _addAllCategoriesToWindow(aWindow) {
+    _addAllCategoriesToDocument(aDocument) {
         for (let categoryName of this.categoryBranch.getChildList("")) {
-            this._addCategoryToWindow(aWindow, categoryName);
+            this._addCategoryToDocument(aDocument, categoryName);
         }
     },
 
     // calICalendarManagerObserver methods
     onCalendarRegistered(aCalendar) {
         this.calendars.add(aCalendar);
         for (let window of this.windows) {
-            this._addCalendarToWindow(window, aCalendar);
+            this._addCalendarToDocument(window.document, aCalendar);
         }
     },
     onCalendarUnregistering(aCalendar) {
         this.calendars.delete(aCalendar);
         for (let window of this.windows) {
-            this._removeCalendarFromWindow(window, aCalendar);
+            this._removeCalendarFromDocument(window.document, aCalendar);
         }
     },
     onCalendarDeleting(aCalendar) {},
 
     // calIObserver methods
     onStartBatch() {},
     onEndBatch() {},
     onLoad() {},
     onAddItem(aItem) {},
     onModifyItem(aNewItem, aOldItem) {},
     onDeleteItem(aDeletedItem) {},
     onError(aCalendar, aErrNo, aMessage) {},
     onPropertyChanged(aCalendar, aName, aValue, aOldValue) {
         if (aName == "color") {
             for (let window of this.windows) {
-                this._addCalendarToWindow(window, aCalendar);
+                this._addCalendarToDocument(window.document, aCalendar);
             }
         }
     },
     onPropertyDeleting(aCalendar, aName) {},
 
     // nsIObserver method
     observe(aSubject, aTopic, aData) {
         if (aTopic == "nsPref:changed") {
             for (let window of this.windows) {
-                this._addCategoryToWindow(window, aData);
+                this._addCategoryToDocument(window.document, aData);
             }
             // TODO Currently, the only way to find out if categories are removed is
             // to initially grab the calendar.categories.names preference and then
             // observe changes to it. It would be better if we had hooks for this.
         } else if (aTopic == "xpcom-shutdown") {
             this.categoryBranch.removeObserver("", this);
             Services.obs.removeObserver(this, "xpcom-shutdown");
         }
--- a/calendar/import-export/calMonthGridPrinter.js
+++ b/calendar/import-export/calMonthGridPrinter.js
@@ -20,16 +20,18 @@ calMonthPrinter.prototype = {
 
     formatToHtml: function(aStream, aStart, aEnd, aCount, aItems, aTitle) {
         let document = cal.xml.parseFile("chrome://calendar-common/skin/printing/calMonthGridPrinter.html");
         let defaultTimezone = cal.dtz.defaultTimezone;
 
         // Set page title
         document.getElementById("title").textContent = aTitle;
 
+        cal.view.colorTracker.addColorsToDocument(document);
+
         // Table that maps YYYY-MM-DD to the DOM node container where items are to be added
         let dayTable = {};
 
         // Make sure to create tables from start to end, if passed
         if (aStart && aEnd) {
             let startDate = this.normalizeStartDate(aStart);
             let endDate = this.normalizeEndDate(aEnd);
             let weekInfoService = cal.getWeekInfoService();
--- a/calendar/import-export/calWeekPrinter.js
+++ b/calendar/import-export/calWeekPrinter.js
@@ -20,16 +20,18 @@ calWeekPrinter.prototype = {
 
     formatToHtml: function(aStream, aStart, aEnd, aCount, aItems, aTitle) {
         let document = cal.xml.parseFile("chrome://calendar-common/skin/printing/calWeekPrinter.html");
         let defaultTimezone = cal.dtz.defaultTimezone;
 
         // Set page title
         document.getElementById("title").textContent = aTitle;
 
+        cal.view.colorTracker.addColorsToDocument(document);
+
         // Table that maps YYYY-MM-DD to the DOM node container where items are to be added
         let dayTable = {};
         let weekInfoService = cal.getWeekInfoService();
 
         // Make sure to create tables from start to end, if passed
         if (aStart && aEnd) {
             for (let current = weekInfoService.getStartOfWeek(aStart); current.compare(aEnd) < 0; current.day += 7) {
                 this.setupWeek(document, current, dayTable);