summaryrefslogtreecommitdiffstats
path: root/devtools/shared/tests/xpcshell/test_css-properties-db.js
blob: 78ed8fd70b6896db1cab9d4c2ea8e635cfe32354 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * Test that the devtool's client-side CSS properties database is in sync with the values
 * on the platform (in Nightly only). If they are not, then `mach devtools-css-db` needs
 * to be run to make everything up to date. Nightly, aurora, beta, and release may have
 * different CSS properties and values. These are based on preferences and compiler flags.
 *
 * This test broke uplifts as the database needed to be regenerated every uplift. The
 * combination of compiler flags and preferences means that it's too difficult to
 * statically determine which properties are enabled between Firefox releases.
 *
 * Because of these difficulties, the database only needs to be up to date with Nightly.
 * It is a fallback that is only used if the remote debugging protocol doesn't support
 * providing a CSS database, so it's ok if the provided properties don't exactly match
 * the inspected target in this particular case.
 */

"use strict";

const {
  PSEUDO_ELEMENTS,
  CSS_PROPERTIES,
} = require("resource://devtools/shared/css/generated/properties-db.js");
const PREFERENCES = InspectorUtils.getCSSPropertyPrefs();
const {
  generateCssProperties,
} = require("resource://devtools/server/actors/css-properties.js");
const { Preferences } = ChromeUtils.importESModule(
  "resource://gre/modules/Preferences.sys.mjs"
);

function run_test() {
  const propertiesErrorMessage =
    "If this assertion fails, then the client side CSS " +
    "properties list in devtools is out of sync with the " +
    "CSS properties on the platform. To fix this " +
    "assertion run `mach devtools-css-db` to re-generate " +
    "the client side properties.";

  // Check that the platform and client match for pseudo elements.
  deepEqual(
    PSEUDO_ELEMENTS,
    InspectorUtils.getCSSPseudoElementNames(),
    "The pseudo elements match on the client and platform. " +
      propertiesErrorMessage
  );

  /**
   * Check that the platform and client match for the details on their CSS properties.
   * Enumerate each property to aid in debugging. Sometimes these properties don't
   * completely agree due to differences in preferences. Check the currently set
   * preference for that property to see if it's enabled.
   */
  const platformProperties = generateCssProperties();

  for (const propertyName in CSS_PROPERTIES) {
    const platformProperty = platformProperties[propertyName];
    const clientProperty = CSS_PROPERTIES[propertyName];
    const deepEqual = isJsonDeepEqual(platformProperty, clientProperty);

    if (deepEqual) {
      ok(true, `The static database and platform match for "${propertyName}".`);
    } else {
      ok(
        false,
        `The static database and platform do not match for ` +
          `
        "${propertyName}". ${propertiesErrorMessage}`
      );
    }
  }

  /**
   * Check that the list of properties on the platform and client are the same. If
   * they are not, check that there may be preferences that are disabling them on the
   * target platform.
   */
  const mismatches = getKeyMismatches(platformProperties, CSS_PROPERTIES)
    // Filter out OS-specific properties.
    .filter(name => name && !name.includes("-moz-osx-"));

  if (mismatches.length === 0) {
    ok(
      true,
      "No client and platform CSS property database mismatches were found."
    );
  }

  mismatches.forEach(propertyName => {
    if (getPreference(propertyName) === false) {
      ok(
        true,
        `The static database and platform do not agree on the property ` +
          `"${propertyName}" This is ok because it is currently disabled through ` +
          `a preference.`
      );
    } else {
      ok(
        false,
        `The static database and platform do not agree on the property ` +
          `"${propertyName}" ${propertiesErrorMessage}`
      );
    }
  });
}

/**
 * Check JSON-serializable objects for deep equality.
 */
function isJsonDeepEqual(a, b) {
  // Handle primitives.
  if (a === b) {
    return true;
  }

  // Handle arrays.
  if (Array.isArray(a) && Array.isArray(b)) {
    if (a.length !== b.length) {
      return false;
    }
    for (let i = 0; i < a.length; i++) {
      if (!isJsonDeepEqual(a[i], b[i])) {
        return false;
      }
    }
    return true;
  }

  // Handle objects
  if (typeof a === "object" && typeof b === "object") {
    for (const key in a) {
      if (!isJsonDeepEqual(a[key], b[key])) {
        return false;
      }
    }

    return Object.keys(a).length === Object.keys(b).length;
  }

  // Not something handled by these cases, therefore not equal.
  return false;
}

/**
 * Take the keys of two objects, and return the ones that don't match.
 *
 * @param {Object} a
 * @param {Object} b
 * @return {Array} keys
 */
function getKeyMismatches(a, b) {
  const aNames = Object.keys(a);
  const bNames = Object.keys(b);
  const aMismatches = aNames.filter(key => !bNames.includes(key));
  const bMismatches = bNames.filter(key => {
    return !aNames.includes(key) && !aMismatches.includes(key);
  });

  return aMismatches.concat(bMismatches);
}

/**
 * Get the preference value of whether this property is enabled. Returns an empty string
 * if no preference exists.
 *
 * @param {String} propertyName
 * @return {Boolean|undefined}
 */
function getPreference(propertyName) {
  const preference = PREFERENCES.find(({ name, pref }) => {
    return name === propertyName && !!pref;
  });

  if (preference) {
    return Preferences.get(preference.pref);
  }
  return undefined;
}