Bug 1433229 - Move view related functions into calViewUtils.jsm - manual changes. r=MakeMyDay
authorPhilipp Kewisch <mozilla@kewis.ch>
Sun, 15 Oct 2017 12:54:54 +0200
changeset 31014 033ede986813abe46b060a294645a46de42b21b1
parent 31013 1794487157f7f65e63a8774bca6035e554d2be8d
child 31015 850b32c6652c3b669154a068534dd1537e52efaa
push id383
push userclokep@gmail.com
push dateMon, 07 May 2018 21:52:48 +0000
reviewersMakeMyDay
bugs1433229
Bug 1433229 - Move view related functions into calViewUtils.jsm - manual changes. r=MakeMyDay MozReview-Commit-ID: H50vfaE3q4k
calendar/base/modules/calPrintUtils.jsm
calendar/base/modules/calUtils.jsm
calendar/base/modules/calUtilsCompat.jsm
calendar/base/modules/calViewUtils.jsm
calendar/base/src/calUtils.js
calendar/providers/gdata/modules/calUtilsShim.jsm
calendar/test/unit/test_view_utils.js
--- a/calendar/base/modules/calPrintUtils.jsm
+++ b/calendar/base/modules/calPrintUtils.jsm
@@ -1,15 +1,14 @@
 /* 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/. */
 
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 Components.utils.import("resource://gre/modules/Preferences.jsm");
-Components.utils.import("resource://calendar/modules/calViewUtils.jsm");
 
 this.EXPORTED_SYMBOLS = ["cal"]; // even though it's defined in calUtils.jsm, import needs this
 cal.print = {
     /**
      * Returns a simple key in the format YYYY-MM-DD for use in the table of
      * dates to day boxes
      *
      * @param dt    The date to translate
--- a/calendar/base/modules/calUtils.jsm
+++ b/calendar/base/modules/calUtils.jsm
@@ -673,61 +673,16 @@ var cal = {
         if (monthForm == "nominative") {
             // Fall back to the default name format
             monthForm = "name";
         }
 
         return cal.calGetString("dateFormat", "month." + aMonthNum + "." + monthForm);
     },
 
-    /**
-     * checks if the mousepointer of an event resides over a XULBox during an event
-     *
-     * @param aMouseEvent   The event eg. a 'mouseout' or 'mousedown' event
-     * @param aXULBox       The xul element
-     * @return              true or false depending on whether the mouse pointer
-     *                      resides over the xulelement
-     */
-    isMouseOverBox: function(aMouseEvent, aXULElement) {
-        let boxObject = aXULElement.boxObject;
-        let boxWidth = boxObject.width;
-        let boxHeight = boxObject.height;
-        let boxScreenX = boxObject.screenX;
-        let boxScreenY = boxObject.screenY;
-        let mouseX = aMouseEvent.screenX;
-        let mouseY = aMouseEvent.screenY;
-        let xIsWithin = (mouseX >= boxScreenX) &&
-                        (mouseX <= (boxScreenX + boxWidth));
-        let yIsWithin = (mouseY >= boxScreenY) &&
-                        (mouseY <= (boxScreenY + boxHeight));
-        return (xIsWithin && yIsWithin);
-    },
-
-    /**
-     * removes those childnodes from a node that contain a specified attribute
-     * and where the value of this attribute matches a passed value
-     * @param aParentNode   The parent node that contains the child nodes in question
-     * @param aAttribute    The name of the attribute
-     * @param aAttribute    The value of the attribute
-     */
-    removeChildElementsByAttribute: function(aParentNode, aAttribute, aValue) {
-        let childNode = aParentNode.lastChild;
-        while (childNode) {
-            let prevChildNode = childNode.previousSibling;
-            if (!aAttribute || aAttribute === undefined) {
-                childNode.remove();
-            } else if (!aValue || aValue === undefined) {
-                childNode.remove();
-            } else if (childNode && childNode.hasAttribute(aAttribute) &&
-                       childNode.getAttribute(aAttribute) == aValue) {
-                childNode.remove();
-            }
-            childNode = prevChildNode;
-        }
-    },
 
     /**
      * Returns the most recent calendar window in an application independent way
      */
     getCalendarWindow: function() {
         return Services.wm.getMostRecentWindow("calendarMainWindow") ||
                Services.wm.getMostRecentWindow("mail:3pane");
     },
@@ -804,16 +759,17 @@ var cal = {
      */
     registerForShutdownCleanup: shutdownCleanup
 };
 
 // Sub-modules for calUtils
 XPCOMUtils.defineLazyModuleGetter(cal, "dtz", "resource://calendar/modules/calDateTimeUtils.jsm", "caldtz");
 XPCOMUtils.defineLazyModuleGetter(cal, "acl", "resource://calendar/modules/calACLUtils.jsm", "calacl");
 XPCOMUtils.defineLazyModuleGetter(cal, "item", "resource://calendar/modules/calItemUtils.jsm", "calitem");
+XPCOMUtils.defineLazyModuleGetter(cal, "view", "resource://calendar/modules/calViewUtils.jsm", "calview");
 
 /**
  * Returns a function that provides access to the given service.
  *
  * @param cid           The contract id to create
  * @param iid           The interface id to create with
  * @return {function}   A function that returns the given service
  */
--- a/calendar/base/modules/calUtilsCompat.jsm
+++ b/calendar/base/modules/calUtilsCompat.jsm
@@ -47,16 +47,28 @@ var migrations = {
         isItemSupported: "isItemSupported",
         isEventCalendar: "isEventCalendar",
         isTaskCalendar: "isTaskCalendar",
         isEvent: "isEvent",
         isToDo: "isToDo",
         checkIfInRange: "checkIfInRange",
         setItemProperty: "setItemProperty",
         getEventDefaultTransparency: "getEventDefaultTransparency"
+    },
+    view: {
+        isMouseOverBox: "isMouseOverBox",
+        calRadioGroupSelectItem: "radioGroupSelectItem",
+        applyAttributeToMenuChildren: "applyAttributeToMenuChildren",
+        removeChildElementsByAttribute: "removeChildElementsByAttribute",
+        getParentNodeOrThis: "getParentNodeOrThis",
+        getParentNodeOrThisByAttribute: "getParentNodeOrThisByAttribute",
+        formatStringForCSSRule: "formatStringForCSSRule",
+        getCompositeCalendar: "getCompositeCalendar",
+        hashColor: "hashColor",
+        getContrastingTextColor: "getContrastingTextColor"
     }
 };
 
 /**
  * Generate a forward function on the given global, for the namespace from the
  * migrations data.
  *
  * @param global        The global object to inject on.
--- a/calendar/base/modules/calViewUtils.jsm
+++ b/calendar/base/modules/calViewUtils.jsm
@@ -1,23 +1,283 @@
 /* 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/. */
 
-Components.utils.import("resource://calendar/modules/calUtils.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "cal", "resource://calendar/modules/calUtils.jsm", "cal");
+
+this.EXPORTED_SYMBOLS = ["calview"]; /* exported calview */
+
+var calview = {
+    /**
+     * Checks if the mousepointer of an event resides over a XULBox during an event
+     *
+     * @param aMouseEvent   The event eg. a 'mouseout' or 'mousedown' event
+     * @param aXULBox       The xul element
+     * @return              true or false depending on whether the mouse pointer
+     *                      resides over the xulelement
+     */
+    isMouseOverBox: function(aMouseEvent, aXULElement) {
+        let boxObject = aXULElement.boxObject;
+        let boxWidth = boxObject.width;
+        let boxHeight = boxObject.height;
+        let boxScreenX = boxObject.screenX;
+        let boxScreenY = boxObject.screenY;
+        let mouseX = aMouseEvent.screenX;
+        let mouseY = aMouseEvent.screenY;
+        let xIsWithin = (mouseX >= boxScreenX) &&
+                        (mouseX <= (boxScreenX + boxWidth));
+        let yIsWithin = (mouseY >= boxScreenY) &&
+                        (mouseY <= (boxScreenY + boxHeight));
+        return (xIsWithin && yIsWithin);
+    },
+
+    /**
+     * Selects an item with id aItemId in the radio group with id aRadioGroupId
+     *
+     * @param aRadioGroupId  the id of the radio group which contains the item
+     * @param aItemId        the item to be selected
+     */
+    radioGroupSelectItem: function(aRadioGroupId, aItemId) {
+        let radioGroup = document.getElementById(aRadioGroupId);
+        let items = radioGroup.getElementsByTagName("radio");
+        let index;
+        for (let i in items) {
+            if (items[i].getAttribute("id") == aItemId) {
+                index = i;
+                break;
+            }
+        }
+        cal.ASSERT(index && index != 0, "Can't find radioGroup item to select.", true);
+        radioGroup.selectedIndex = index;
+    },
 
-this.EXPORTED_SYMBOLS = ["cal"];
-cal.view = {
+    /**
+     * Applies a value to all children of a Menu. If the respective childnodes define
+     * a command the value is applied to the attribute of thecommand of the childnode
+     *
+     * @param aElement The parentnode of the elements
+     * @param aAttributeName The name of the attribute
+     * @param aValue The value of the attribute
+     */
+    applyAttributeToMenuChildren: function(aElement, aAttributeName, aValue) {
+        let sibling = aElement.firstChild;
+        do {
+            if (sibling) {
+                let domObject = sibling;
+                let commandName = null;
+                if (sibling.hasAttribute("command")) {
+                    commandName = sibling.getAttribute("command");
+                }
+                if (commandName) {
+                    let command = document.getElementById(commandName);
+                    if (command) {
+                        domObject = command;
+                    }
+                }
+                domObject.setAttribute(aAttributeName, aValue);
+                sibling = sibling.nextSibling;
+            }
+        } while (sibling);
+    },
+
+    /**
+     * Removes those childnodes from a node that contain a specified attribute
+     * and where the value of this attribute matches a passed value
+     *
+     * @param aParentNode   The parent node that contains the child nodes in question
+     * @param aAttribute    The name of the attribute
+     * @param aAttribute    The value of the attribute
+     */
+    removeChildElementsByAttribute: function(aParentNode, aAttribute, aValue) {
+        let childNode = aParentNode.lastChild;
+        while (childNode) {
+            let prevChildNode = childNode.previousSibling;
+            if (!aAttribute || aAttribute === undefined) {
+                childNode.remove();
+            } else if (!aValue || aValue === undefined) {
+                childNode.remove();
+            } else if (childNode && childNode.hasAttribute(aAttribute) &&
+                       childNode.getAttribute(aAttribute) == aValue) {
+                childNode.remove();
+            }
+            childNode = prevChildNode;
+        }
+    },
+
+    /**
+     * Returns a parentnode - or the passed node - with the given localName, by
+     * traversing up the DOM hierarchy.
+     *
+     * @param aChildNode  The childnode.
+     * @param aLocalName  The localName of the to-be-returned parent
+     *                      that is looked for.
+     * @return            The parent with the given localName or the
+     *                      given childNode 'aChildNode'. If no appropriate
+     *                      parent node with aLocalName could be
+     *                      retrieved it is returned 'null'.
+     */
+    getParentNodeOrThis: function(aChildNode, aLocalName) {
+        let node = aChildNode;
+        while (node && (node.localName != aLocalName)) {
+            node = node.parentNode;
+            if (node.tagName == undefined) {
+                return null;
+            }
+        }
+        return node;
+    },
+
     /**
-      - * Item comparator for inserting items into dayboxes.
-      - *
-      - * @param a     The first item
-      - * @param b     The second item
-      - * @return      The usual -1, 0, 1
-      - */
+     * Returns a parentnode  - or the passed node -  with the given attribute
+     * value for the given attributename by traversing up the DOM hierarchy.
+     *
+     * @param aChildNode      The childnode.
+     * @param aAttibuteName   The name of the attribute that is to be compared with
+     * @param aAttibuteValue  The value of the attribute that is to be compared with
+     * @return                The parent with the given attributeName set that has
+     *                          the same value as the given given attributevalue
+     *                          'aAttributeValue'. If no appropriate
+     *                          parent node can be retrieved it is returned 'null'.
+     */
+    getParentNodeOrThisByAttribute: function(aChildNode, aAttributeName, aAttributeValue) {
+        let node = aChildNode;
+        while (node && (node.getAttribute(aAttributeName) != aAttributeValue)) {
+            node = node.parentNode;
+            if (node.tagName == undefined) {
+                return null;
+            }
+        }
+        return node;
+    },
+
+    /**
+     * Format the given string to work inside a CSS rule selector
+     * (and as part of a non-unicode preference key).
+     *
+     * Replaces each space ' ' char with '_'.
+     * Replaces each char other than ascii digits and letters, with '-uxHHH-'
+     * where HHH is unicode in hexadecimal (variable length, terminated by the '-').
+     *
+     * Ensures: result only contains ascii digits, letters,'-', and '_'.
+     * Ensures: result is invertible, so (f(a) = f(b)) implies (a = b).
+     *   also means f is not idempotent, so (a != f(a)) implies (f(a) != f(f(a))).
+     * Ensures: result must be lowercase.
+     * Rationale: preference keys require 8bit chars, and ascii chars are legible
+     *              in most fonts (in case user edits PROFILE/prefs.js).
+     *            CSS class names in Gecko 1.8 seem to require lowercase,
+     *              no punctuation, and of course no spaces.
+     *   nmchar            [_a-zA-Z0-9-]|{nonascii}|{escape}
+     *   name              {nmchar}+
+     *   http://www.w3.org/TR/CSS21/grammar.html#scanner
+     *
+     * @param aString       The unicode string to format
+     * @return              The formatted string using only chars [_a-zA-Z0-9-]
+     */
+    formatStringForCSSRule: function(aString) {
+        function toReplacement(char) {
+            // char code is natural number (positive integer)
+            let nat = char.charCodeAt(0);
+            switch (nat) {
+                case 0x20: // space
+                    return "_";
+                default:
+                    return "-ux" + nat.toString(16) + "-"; // lowercase
+            }
+        }
+        // Result must be lowercase or style rule will not work.
+        return aString.toLowerCase().replace(/[^a-zA-Z0-9]/g, toReplacement);
+    },
+
+    /**
+     * Gets the cached instance of the composite calendar.
+     *
+     * @param aWindow       The window to get the composite calendar for.
+     */
+    getCompositeCalendar: function(aWindow) {
+        if (typeof aWindow._compositeCalendar == "undefined") {
+            let comp = aWindow._compositeCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
+                                                              .createInstance(Components.interfaces.calICompositeCalendar);
+            comp.prefPrefix = "calendar-main";
+
+            if (typeof aWindow.gCalendarStatusFeedback != "undefined") {
+                // If we are in a window that has calendar status feedback, set
+                // up our status observer.
+                let chromeWindow = aWindow.QueryInterface(Components.interfaces.nsIDOMChromeWindow);
+                comp.setStatusObserver(aWindow.gCalendarStatusFeedback, chromeWindow);
+            }
+        }
+        return aWindow._compositeCalendar;
+    },
+
+    /**
+     * Hash the given string into a color from the color palette of the standard
+     * color picker.
+     *
+     * @param str           The string to hash into a color.
+     * @return              The hashed color.
+     */
+    hashColor: function(str) {
+        // This is the palette of colors in the current colorpicker implementation.
+        // Unfortunately, there is no easy way to extract these colors from the
+        // binding directly.
+        const colorPalette = [
+            "#FFFFFF", "#FFCCCC", "#FFCC99", "#FFFF99", "#FFFFCC",
+            "#99FF99", "#99FFFF", "#CCFFFF", "#CCCCFF", "#FFCCFF",
+            "#CCCCCC", "#FF6666", "#FF9966", "#FFFF66", "#FFFF33",
+            "#66FF99", "#33FFFF", "#66FFFF", "#9999FF", "#FF99FF",
+            "#C0C0C0", "#FF0000", "#FF9900", "#FFCC66", "#FFFF00",
+            "#33FF33", "#66CCCC", "#33CCFF", "#6666CC", "#CC66CC",
+            "#999999", "#CC0000", "#FF6600", "#FFCC33", "#FFCC00",
+            "#33CC00", "#00CCCC", "#3366FF", "#6633FF", "#CC33CC",
+            "#666666", "#990000", "#CC6600", "#CC9933", "#999900",
+            "#009900", "#339999", "#3333FF", "#6600CC", "#993399",
+            "#333333", "#660000", "#993300", "#996633", "#666600",
+            "#006600", "#336666", "#000099", "#333399", "#663366",
+            "#000000", "#330000", "#663300", "#663333", "#333300",
+            "#003300", "#003333", "#000066", "#330099", "#330033"
+        ];
+
+        let sum = Array.from(str || " ", e => e.charCodeAt(0)).reduce((a, b) => a + b);
+        return colorPalette[sum % colorPalette.length];
+    },
+
+    /**
+     * Pick whichever of "black" or "white" will look better when used as a text
+     * color against a background of bgColor.
+     *
+     * @param bgColor   the background color as a "#RRGGBB" string
+     */
+    getContrastingTextColor: function(bgColor) {
+        let calcColor = bgColor.replace(/#/g, "");
+        let red = parseInt(calcColor.substring(0, 2), 16);
+        let green = parseInt(calcColor.substring(2, 4), 16);
+        let blue = parseInt(calcColor.substring(4, 6), 16);
+
+        // Calculate the brightness (Y) value using the YUV color system.
+        let brightness = (0.299 * red) + (0.587 * green) + (0.114 * blue);
+
+        // Consider all colors with less than 56% brightness as dark colors and
+        // use white as the foreground color, otherwise use black.
+        if (brightness < 144) {
+            return "white";
+        }
+
+        return "black";
+    },
+
+    /**
+      * Item comparator for inserting items into dayboxes.
+      *
+      * @param a     The first item
+      * @param b     The second item
+      * @return      The usual -1, 0, 1
+      */
     compareItems: function(a, b) {
         if (!a) {
             return -1;
         }
         if (!b) {
             return 1;
         }
 
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -18,53 +18,16 @@
  */
 
 Components.utils.import("resource:///modules/mailServices.js");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/Preferences.jsm");
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
 
-/**
- * Format the given string to work inside a CSS rule selector
- * (and as part of a non-unicode preference key).
- *
- * Replaces each space ' ' char with '_'.
- * Replaces each char other than ascii digits and letters, with '-uxHHH-'
- * where HHH is unicode in hexadecimal (variable length, terminated by the '-').
- *
- * Ensures: result only contains ascii digits, letters,'-', and '_'.
- * Ensures: result is invertible, so (f(a) = f(b)) implies (a = b).
- *   also means f is not idempotent, so (a != f(a)) implies (f(a) != f(f(a))).
- * Ensures: result must be lowercase.
- * Rationale: preference keys require 8bit chars, and ascii chars are legible
- *              in most fonts (in case user edits PROFILE/prefs.js).
- *            CSS class names in Gecko 1.8 seem to require lowercase,
- *              no punctuation, and of course no spaces.
- *   nmchar            [_a-zA-Z0-9-]|{nonascii}|{escape}
- *   name              {nmchar}+
- *   http://www.w3.org/TR/CSS21/grammar.html#scanner
- *
- * @param aString       The unicode string to format
- * @return              The formatted string using only chars [_a-zA-Z0-9-]
- */
-function formatStringForCSSRule(aString) {
-    function toReplacement(char) {
-        // char code is natural number (positive integer)
-        let nat = char.charCodeAt(0);
-        switch (nat) {
-            case 0x20: // space
-                return "_";
-            default:
-                return "-ux" + nat.toString(16) + "-"; // lowercase
-        }
-    }
-    // Result must be lowercase or style rule will not work.
-    return aString.toLowerCase().replace(/[^a-zA-Z0-9]/g, toReplacement);
-}
 
 /**
  * Shared dialog functions
  * Gets the calendar directory, defaults to <profile-dir>/calendar
  */
 function getCalendarDirectory() {
     if (getCalendarDirectory.mDir === undefined) {
         let dir = Services.dirsvc.get("ProfD", Components.interfaces.nsIFile);
@@ -155,36 +118,16 @@ function calPrint(aWindow) {
                        "centerscreen,chrome,resizable");
 }
 
 /**
  * Other functions
  */
 
 /**
- * Selects an item with id aItemId in the radio group with id aRadioGroupId
- *
- * @param aRadioGroupId  the id of the radio group which contains the item
- * @param aItemId        the item to be selected
- */
-function calRadioGroupSelectItem(aRadioGroupId, aItemId) {
-    let radioGroup = document.getElementById(aRadioGroupId);
-    let items = radioGroup.getElementsByTagName("radio");
-    let index;
-    for (let i in items) {
-        if (items[i].getAttribute("id") == aItemId) {
-            index = i;
-            break;
-        }
-    }
-    ASSERT(index && index != 0, "Can't find radioGroup item to select.", true);
-    radioGroup.selectedIndex = index;
-}
-
-/**
  * Get array of category names from preferences or locale default,
  * unescaping any commas in each category name.
  * @return array of category names
  */
 function getPrefCategoriesArray() {
     let categories = Preferences.get("calendar.categories.names", null);
 
     // If no categories are configured load a default set from properties file
@@ -202,58 +145,26 @@ function getPrefCategoriesArray() {
 function setupDefaultCategories() {
     // First, set up the category names
     let categories = calGetString("categories", "categories2");
     Preferences.set("calendar.categories.names", categories);
 
     // Now, initialize the category default colors
     let categoryArray = categoriesStringToArray(categories);
     for (let category of categoryArray) {
-        let prefName = formatStringForCSSRule(category);
+        let prefName = cal.view.formatStringForCSSRule(category);
         Preferences.set("calendar.category.color." + prefName,
-                        hashColor(category));
+                        cal.view.hashColor(category));
     }
 
     // Return the list of categories for further processing
     return categories;
 }
 
 /**
- * Hash the given string into a color from the color palette of the standard
- * color picker.
- *
- * @param str           The string to hash into a color.
- * @return              The hashed color.
- */
-function hashColor(str) {
-    // This is the palette of colors in the current colorpicker implementation.
-    // Unfortunately, there is no easy way to extract these colors from the
-    // binding directly.
-    const colorPalette = [
-        "#FFFFFF", "#FFCCCC", "#FFCC99", "#FFFF99", "#FFFFCC",
-        "#99FF99", "#99FFFF", "#CCFFFF", "#CCCCFF", "#FFCCFF",
-        "#CCCCCC", "#FF6666", "#FF9966", "#FFFF66", "#FFFF33",
-        "#66FF99", "#33FFFF", "#66FFFF", "#9999FF", "#FF99FF",
-        "#C0C0C0", "#FF0000", "#FF9900", "#FFCC66", "#FFFF00",
-        "#33FF33", "#66CCCC", "#33CCFF", "#6666CC", "#CC66CC",
-        "#999999", "#CC0000", "#FF6600", "#FFCC33", "#FFCC00",
-        "#33CC00", "#00CCCC", "#3366FF", "#6633FF", "#CC33CC",
-        "#666666", "#990000", "#CC6600", "#CC9933", "#999900",
-        "#009900", "#339999", "#3333FF", "#6600CC", "#993399",
-        "#333333", "#660000", "#993300", "#996633", "#666600",
-        "#006600", "#336666", "#000099", "#333399", "#663366",
-        "#000000", "#330000", "#663300", "#663333", "#333300",
-        "#003300", "#003333", "#000066", "#330099", "#330033"
-    ];
-
-    let sum = Array.from(str || " ", e => e.charCodeAt(0)).reduce((a, b) => a + b);
-    return colorPalette[sum % colorPalette.length];
-}
-
-/**
  * Convert categories string to list of category names.
  *
  * Stored categories may include escaped commas within a name.
  * Split categories string at commas, but not at escaped commas (\,).
  * Afterward, replace escaped commas (\,) with commas (,) in each name.
  * @param aCategoriesPrefValue string from "calendar.categories.names" pref,
  * which may contain escaped commas (\,) in names.
  * @return list of category names
@@ -509,40 +420,16 @@ function ASSERT(aCondition, aMessage, aC
  *
  * @param aMsg The message to be shown
  * @param aWindow The window to show the message in, or null for any window.
  */
 function showError(aMsg, aWindow=null) {
     Services.prompt.alert(aWindow, cal.calGetString("calendar", "genericErrorTitle"), aMsg);
 }
 
-/**
- * Pick whichever of "black" or "white" will look better when used as a text
- * color against a background of bgColor.
- *
- * @param bgColor   the background color as a "#RRGGBB" string
- */
-function getContrastingTextColor(bgColor) {
-    let calcColor = bgColor.replace(/#/g, "");
-    let red = parseInt(calcColor.substring(0, 2), 16);
-    let green = parseInt(calcColor.substring(2, 4), 16);
-    let blue = parseInt(calcColor.substring(4, 6), 16);
-
-    // Calculate the brightness (Y) value using the YUV color system.
-    let brightness = (0.299 * red) + (0.587 * green) + (0.114 * blue);
-
-    // Consider all colors with less than 56% brightness as dark colors and
-    // use white as the foreground color, otherwise use black.
-    if (brightness < 144) {
-        return "white";
-    }
-
-    return "black";
-}
-
 function calInterfaceBag(iid) {
     this.init(iid);
 }
 calInterfaceBag.prototype = {
     mIid: null,
     mInterfaces: null,
 
     // Iterating the inteface bag iterates the interfaces it contains
@@ -718,45 +605,16 @@ calOperationGroup.prototype = {
     }
 };
 
 /**
  * TODO: The following UI-related functions need to move somewhere different,
  * i.e calendar-ui-utils.js
  */
 
-/**
- * applies a value to all children of a Menu. If the respective childnodes define
- * a command the value is applied to the attribute of thecommand of the childnode
- *
- * @param aElement The parentnode of the elements
- * @param aAttributeName The name of the attribute
- * @param aValue The value of the attribute
- */
-function applyAttributeToMenuChildren(aElement, aAttributeName, aValue) {
-    let sibling = aElement.firstChild;
-    do {
-        if (sibling) {
-            let domObject = sibling;
-            let commandName = null;
-            if (sibling.hasAttribute("command")) {
-                commandName = sibling.getAttribute("command");
-            }
-            if (commandName) {
-                let command = document.getElementById(commandName);
-                if (command) {
-                    domObject = command;
-                }
-            }
-            domObject.setAttribute(aAttributeName, aValue);
-            sibling = sibling.nextSibling;
-        }
-    } while (sibling);
-}
-
 
 /**
  * compares the value of a property of an array of objects and returns
  * true or false if it is same or not among all array members
  *
  * @param aObjects An Array of Objects to inspect
  * @param aProperty Name the name of the Property of which the value is compared
  */
@@ -770,62 +628,16 @@ function isPropertyValueSame(aObjects, a
         if (compValue != value) {
             return false;
         }
     }
     return true;
 }
 
 /**
- * returns a parentnode - or the overgiven node - with the given localName,
- * by "walking up" the DOM-hierarchy.
- *
- * @param aChildNode  The childnode.
- * @param aLocalName  The localName of the to-be-returned parent
- *                      that is looked for.
- * @return            The parent with the given localName or the
- *                      given childNode 'aChildNode'. If no appropriate
- *                      parent node with aLocalName could be
- *                      retrieved it is returned 'null'.
- */
-function getParentNodeOrThis(aChildNode, aLocalName) {
-    let node = aChildNode;
-    while (node && (node.localName != aLocalName)) {
-        node = node.parentNode;
-        if (node.tagName == undefined) {
-            return null;
-        }
-    }
-    return node;
-}
-
-/**
- * Returns a parentnode  - or the overgiven node -  with the given attributevalue
- * for the given attributename by "walking up" the DOM-hierarchy.
- *
- * @param aChildNode      The childnode.
- * @param aAttibuteName   The name of the attribute that is to be compared with
- * @param aAttibuteValue  The value of the attribute that is to be compared with
- * @return                The parent with the given attributeName set that has
- *                          the same value as the given given attributevalue
- *                          'aAttributeValue'. If no appropriate
- *                          parent node can be retrieved it is returned 'null'.
- */
-function getParentNodeOrThisByAttribute(aChildNode, aAttributeName, aAttributeValue) {
-    let node = aChildNode;
-    while (node && (node.getAttribute(aAttributeName) != aAttributeValue)) {
-        node = node.parentNode;
-        if (node.tagName == undefined) {
-            return null;
-        }
-    }
-    return node;
-}
-
-/**
  * END TODO: The above UI-related functions need to move somewhere different,
  * i.e calendar-ui-utils.js
  */
 
 /**
  * Implements a property bag.
  */
 function calPropertyBag() {
@@ -1020,29 +832,8 @@ function binaryInsert(itemArray, item, c
     } else if (!discardDuplicates ||
                 comptor(itemArray[Math.min(newIndex, itemArray.length - 1)], item) != 0) {
         // Only add the item if duplicates should not be discarded, or if
         // they should and itemArray[newIndex] != item.
         itemArray.splice(newIndex, 0, item);
     }
     return newIndex;
 }
-
-/**
- * Gets the cached instance of the composite calendar.
- *
- * @param aWindow       The window to get the composite calendar for.
- */
-function getCompositeCalendar(aWindow) {
-    if (typeof aWindow._compositeCalendar == "undefined") {
-        let comp = aWindow._compositeCalendar = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
-                                                          .createInstance(Components.interfaces.calICompositeCalendar);
-        comp.prefPrefix = "calendar-main";
-
-        if (typeof aWindow.gCalendarStatusFeedback != "undefined") {
-            // If we are in a window that has calendar status feedback, set
-            // up our status observer.
-            let chromeWindow = aWindow.QueryInterface(Components.interfaces.nsIDOMChromeWindow);
-            comp.setStatusObserver(aWindow.gCalendarStatusFeedback, chromeWindow);
-        }
-    }
-    return aWindow._compositeCalendar;
-}
--- a/calendar/providers/gdata/modules/calUtilsShim.jsm
+++ b/calendar/providers/gdata/modules/calUtilsShim.jsm
@@ -43,8 +43,24 @@ if (!cal.item) {
         serialize: (...args) => cal.getSerializedItem(...args),
         get productId() { return cal.calGetProductId(); },
         get productVersion() { return cal.calGetProductVersion(); },
         setStaticProps: (...args) => cal.calSetProdidVersion(...args),
         findWindow: (...args) => cal.findItemWindow(...args),
         setToAllDay: (...args) => cal.setItemToAllDay(...args)
     };
 }
+
+if (!cal.view || !cal.view.hashColor) {
+    cal.view = Object.assign(cal.view || {}, {
+        isMouseOverBox: (...args) => cal.isMouseOverBox(...args),
+        radioGroupSelectItem: (...args) => cal.calRadioGroupSelectItem(...args),
+        applyAttributeToMenuChildren: (...args) => cal.applyAttributeToMenuChildren(...args),
+        removeChildElementsByAttribute: (...args) => cal.removeChildElementsByAttribute(...args),
+        getParentNodeOrThis: (...args) => cal.getParentNodeOrThis(...args),
+        getParentNodeOrThisByAttribute: (...args) => cal.getParentNodeOrThisByAttribute(...args),
+        formatStringForCSSRule: (...args) => cal.formatStringForCSSRule(...args),
+        getCompositeCalendar: (...args) => cal.getCompositeCalendar(...args),
+        hashColor: (...args) => cal.hashColor(...args),
+        getContrastingTextColor: (...args) => cal.getContrastingTextColor(...args),
+        /* cal.view.compareItems stays the same, just a different import */
+    });
+}
--- a/calendar/test/unit/test_view_utils.js
+++ b/calendar/test/unit/test_view_utils.js
@@ -1,14 +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/. */
 
-Components.utils.import("resource://calendar/modules/calViewUtils.jsm");
-
 function run_test() {
     test_not_a_date();
     test_compare_event_and_todo();
     test_compare_startdate();
     test_compare_enddate();
     test_compare_alldayevent();
     test_compare_title();
     test_compare_todo();