1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5"use strict";
6
7const { Preferences } = ChromeUtils.import(
8  "resource://gre/modules/Preferences.jsm"
9);
10const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
11const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
12const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
13ChromeUtils.defineModuleGetter(
14  this,
15  "FileTestUtils",
16  "resource://testing-common/FileTestUtils.jsm"
17);
18
19var EXPORTED_SYMBOLS = ["EnterprisePolicyTesting", "PoliciesPrefTracker"];
20
21var EnterprisePolicyTesting = {
22  // |json| must be an object representing the desired policy configuration, OR a
23  // path to the JSON file containing the policy configuration.
24  setupPolicyEngineWithJson: async function setupPolicyEngineWithJson(
25    json,
26    customSchema
27  ) {
28    let filePath;
29    if (typeof json == "object") {
30      filePath = FileTestUtils.getTempFile("policies.json").path;
31
32      // This file gets automatically deleted by FileTestUtils
33      // at the end of the test run.
34      await OS.File.writeAtomic(filePath, JSON.stringify(json), {
35        encoding: "utf-8",
36      });
37    } else {
38      filePath = json;
39    }
40
41    Services.prefs.setStringPref("browser.policies.alternatePath", filePath);
42
43    let promise = new Promise(resolve => {
44      Services.obs.addObserver(function observer() {
45        Services.obs.removeObserver(
46          observer,
47          "EnterprisePolicies:AllPoliciesApplied"
48        );
49        resolve();
50      }, "EnterprisePolicies:AllPoliciesApplied");
51    });
52
53    // Clear any previously used custom schema
54    Cu.unload("resource:///modules/policies/schema.jsm");
55
56    if (customSchema) {
57      let schemaModule = ChromeUtils.import(
58        "resource:///modules/policies/schema.jsm",
59        null
60      );
61      schemaModule.schema = customSchema;
62    }
63
64    Services.obs.notifyObservers(null, "EnterprisePolicies:Restart");
65    return promise;
66  },
67
68  checkPolicyPref(prefName, expectedValue, expectedLockedness) {
69    if (expectedLockedness !== undefined) {
70      Assert.equal(
71        Preferences.locked(prefName),
72        expectedLockedness,
73        `Pref ${prefName} is correctly locked/unlocked`
74      );
75    }
76
77    Assert.equal(
78      Preferences.get(prefName),
79      expectedValue,
80      `Pref ${prefName} has the correct value`
81    );
82  },
83
84  resetRunOnceState: function resetRunOnceState() {
85    const runOnceBaseKeys = [
86      "browser.policies.runonce.",
87      "browser.policies.runOncePerModification.",
88    ];
89    for (let base of runOnceBaseKeys) {
90      for (let key of Services.prefs.getChildList(base)) {
91        if (Services.prefs.prefHasUserValue(key)) {
92          Services.prefs.clearUserPref(key);
93        }
94      }
95    }
96  },
97};
98
99/**
100 * This helper will track prefs that have been changed
101 * by the policy engine through the setAndLockPref and
102 * setDefaultPref APIs (from Policies.jsm) and make sure
103 * that they are restored to their original values when
104 * the test ends or another test case restarts the engine.
105 */
106var PoliciesPrefTracker = {
107  _originalFunc: null,
108  _originalValues: new Map(),
109
110  start() {
111    let PoliciesBackstage = ChromeUtils.import(
112      "resource:///modules/policies/Policies.jsm",
113      null
114    );
115    this._originalFunc = PoliciesBackstage.setDefaultPref;
116    PoliciesBackstage.setDefaultPref = this.hoistedSetDefaultPref.bind(this);
117  },
118
119  stop() {
120    this.restoreDefaultValues();
121
122    let PoliciesBackstage = ChromeUtils.import(
123      "resource:///modules/policies/Policies.jsm",
124      null
125    );
126    PoliciesBackstage.setDefaultPref = this._originalFunc;
127    this._originalFunc = null;
128  },
129
130  hoistedSetDefaultPref(prefName, prefValue, locked = false) {
131    // If this pref is seen multiple times, the very first
132    // value seen is the one that is actually the default.
133    if (!this._originalValues.has(prefName)) {
134      let defaults = new Preferences({ defaultBranch: true });
135      let stored = {};
136
137      if (defaults.has(prefName)) {
138        stored.originalDefaultValue = defaults.get(prefName);
139      } else {
140        stored.originalDefaultValue = undefined;
141      }
142
143      if (
144        Preferences.isSet(prefName) &&
145        Preferences.get(prefName) == prefValue
146      ) {
147        // If a user value exists, and we're changing the default
148        // value to be th same as the user value, that will cause
149        // the user value to be dropped. In that case, let's also
150        // store it to ensure that we restore everything correctly.
151        stored.originalUserValue = Preferences.get(prefName);
152      }
153
154      this._originalValues.set(prefName, stored);
155    }
156
157    // Now that we've stored the original values, call the
158    // original setDefaultPref function.
159    this._originalFunc(prefName, prefValue, locked);
160  },
161
162  restoreDefaultValues() {
163    let defaults = new Preferences({ defaultBranch: true });
164
165    for (let [prefName, stored] of this._originalValues) {
166      // If a pref was used through setDefaultPref instead
167      // of setAndLockPref, it wasn't locked, but calling
168      // unlockPref is harmless
169      Preferences.unlock(prefName);
170
171      if (stored.originalDefaultValue !== undefined) {
172        defaults.set(prefName, stored.originalDefaultValue);
173      } else {
174        Services.prefs.getDefaultBranch("").deleteBranch(prefName);
175      }
176
177      if (stored.originalUserValue !== undefined) {
178        Preferences.set(prefName, stored.originalUserValue);
179      }
180    }
181
182    this._originalValues.clear();
183  },
184};
185