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