summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/shim
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/shim')
-rw-r--r--devtools/client/shared/shim/Services.js620
-rw-r--r--devtools/client/shared/shim/moz.build13
-rw-r--r--devtools/client/shared/shim/test/.eslintrc.js6
-rw-r--r--devtools/client/shared/shim/test/file_service_wm.html20
-rw-r--r--devtools/client/shared/shim/test/mochitest.ini10
-rw-r--r--devtools/client/shared/shim/test/prefs-wrapper.js80
-rw-r--r--devtools/client/shared/shim/test/test_service_appinfo.html29
-rw-r--r--devtools/client/shared/shim/test/test_service_focus.html78
-rw-r--r--devtools/client/shared/shim/test/test_service_prefs.html244
-rw-r--r--devtools/client/shared/shim/test/test_service_prefs_defaults.html71
-rw-r--r--devtools/client/shared/shim/test/test_service_wm.html36
11 files changed, 1207 insertions, 0 deletions
diff --git a/devtools/client/shared/shim/Services.js b/devtools/client/shared/shim/Services.js
new file mode 100644
index 000000000..97ea51433
--- /dev/null
+++ b/devtools/client/shared/shim/Services.js
@@ -0,0 +1,620 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+/* globals localStorage, window, document, NodeFilter */
+
+// Some constants from nsIPrefBranch.idl.
+const PREF_INVALID = 0;
+const PREF_STRING = 32;
+const PREF_INT = 64;
+const PREF_BOOL = 128;
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+// We prefix all our local storage items with this.
+const PREFIX = "Services.prefs:";
+
+/**
+ * Create a new preference branch. This object conforms largely to
+ * nsIPrefBranch and nsIPrefService, though it only implements the
+ * subset needed by devtools. A preference branch can hold child
+ * preferences while also holding a preference value itself.
+ *
+ * @param {PrefBranch} parent the parent branch, or null for the root
+ * branch.
+ * @param {String} name the base name of this branch
+ * @param {String} fullName the fully-qualified name of this branch
+ */
+function PrefBranch(parent, name, fullName) {
+ this._parent = parent;
+ this._name = name;
+ this._fullName = fullName;
+ this._observers = {};
+ this._children = {};
+
+ // Properties used when this branch has a value as well.
+ this._defaultValue = null;
+ this._hasUserValue = false;
+ this._userValue = null;
+ this._type = PREF_INVALID;
+}
+
+PrefBranch.prototype = {
+ PREF_INVALID: PREF_INVALID,
+ PREF_STRING: PREF_STRING,
+ PREF_INT: PREF_INT,
+ PREF_BOOL: PREF_BOOL,
+
+ /** @see nsIPrefBranch.root. */
+ get root() {
+ return this._fullName;
+ },
+
+ /** @see nsIPrefBranch.getPrefType. */
+ getPrefType: function (prefName) {
+ return this._findPref(prefName)._type;
+ },
+
+ /** @see nsIPrefBranch.getBoolPref. */
+ getBoolPref: function (prefName) {
+ let thePref = this._findPref(prefName);
+ if (thePref._type !== PREF_BOOL) {
+ throw new Error(`${prefName} does not have bool type`);
+ }
+ return thePref._get();
+ },
+
+ /** @see nsIPrefBranch.setBoolPref. */
+ setBoolPref: function (prefName, value) {
+ if (typeof value !== "boolean") {
+ throw new Error("non-bool passed to setBoolPref");
+ }
+ let thePref = this._findOrCreatePref(prefName, value, true, value);
+ if (thePref._type !== PREF_BOOL) {
+ throw new Error(`${prefName} does not have bool type`);
+ }
+ thePref._set(value);
+ },
+
+ /** @see nsIPrefBranch.getCharPref. */
+ getCharPref: function (prefName) {
+ let thePref = this._findPref(prefName);
+ if (thePref._type !== PREF_STRING) {
+ throw new Error(`${prefName} does not have string type`);
+ }
+ return thePref._get();
+ },
+
+ /** @see nsIPrefBranch.setCharPref. */
+ setCharPref: function (prefName, value) {
+ if (typeof value !== "string") {
+ throw new Error("non-string passed to setCharPref");
+ }
+ let thePref = this._findOrCreatePref(prefName, value, true, value);
+ if (thePref._type !== PREF_STRING) {
+ throw new Error(`${prefName} does not have string type`);
+ }
+ thePref._set(value);
+ },
+
+ /** @see nsIPrefBranch.getIntPref. */
+ getIntPref: function (prefName) {
+ let thePref = this._findPref(prefName);
+ if (thePref._type !== PREF_INT) {
+ throw new Error(`${prefName} does not have int type`);
+ }
+ return thePref._get();
+ },
+
+ /** @see nsIPrefBranch.setIntPref. */
+ setIntPref: function (prefName, value) {
+ if (typeof value !== "number") {
+ throw new Error("non-number passed to setIntPref");
+ }
+ let thePref = this._findOrCreatePref(prefName, value, true, value);
+ if (thePref._type !== PREF_INT) {
+ throw new Error(`${prefName} does not have int type`);
+ }
+ thePref._set(value);
+ },
+
+ /** @see nsIPrefBranch.clearUserPref */
+ clearUserPref: function (prefName) {
+ let thePref = this._findPref(prefName);
+ thePref._clearUserValue();
+ },
+
+ /** @see nsIPrefBranch.prefHasUserValue */
+ prefHasUserValue: function (prefName) {
+ let thePref = this._findPref(prefName);
+ return thePref._hasUserValue;
+ },
+
+ /** @see nsIPrefBranch.addObserver */
+ addObserver: function (domain, observer, holdWeak) {
+ if (holdWeak) {
+ throw new Error("shim prefs only supports strong observers");
+ }
+
+ if (!(domain in this._observers)) {
+ this._observers[domain] = [];
+ }
+ this._observers[domain].push(observer);
+ },
+
+ /** @see nsIPrefBranch.removeObserver */
+ removeObserver: function (domain, observer) {
+ if (!(domain in this._observers)) {
+ return;
+ }
+ let index = this._observers[domain].indexOf(observer);
+ if (index >= 0) {
+ this._observers[domain].splice(index, 1);
+ }
+ },
+
+ /** @see nsIPrefService.savePrefFile */
+ savePrefFile: function (file) {
+ if (file) {
+ throw new Error("shim prefs only supports null file in savePrefFile");
+ }
+ // Nothing to do - this implementation always writes back.
+ },
+
+ /** @see nsIPrefService.getBranch */
+ getBranch: function (prefRoot) {
+ if (!prefRoot) {
+ return this;
+ }
+ if (prefRoot.endsWith(".")) {
+ prefRoot = prefRoot.slice(0, -1);
+ }
+ // This is a bit weird since it could erroneously return a pref,
+ // not a pref branch.
+ return this._findPref(prefRoot);
+ },
+
+ /**
+ * Return this preference's current value.
+ *
+ * @return {Any} The current value of this preference. This may
+ * return a string, a number, or a boolean depending on the
+ * preference's type.
+ */
+ _get: function () {
+ if (this._hasUserValue) {
+ return this._userValue;
+ }
+ return this._defaultValue;
+ },
+
+ /**
+ * Set the preference's value. The new value is assumed to be a
+ * user value. After setting the value, this function emits a
+ * change notification.
+ *
+ * @param {Any} value the new value
+ */
+ _set: function (value) {
+ if (!this._hasUserValue || value !== this._userValue) {
+ this._userValue = value;
+ this._hasUserValue = true;
+ this._saveAndNotify();
+ }
+ },
+
+ /**
+ * Set the default value for this preference, and emit a
+ * notification if this results in a visible change.
+ *
+ * @param {Any} value the new default value
+ */
+ _setDefault: function (value) {
+ if (this._defaultValue !== value) {
+ this._defaultValue = value;
+ if (!this._hasUserValue) {
+ this._saveAndNotify();
+ }
+ }
+ },
+
+ /**
+ * If this preference has a user value, clear it. If a change was
+ * made, emit a change notification.
+ */
+ _clearUserValue: function () {
+ if (this._hasUserValue) {
+ this._userValue = null;
+ this._hasUserValue = false;
+ this._saveAndNotify();
+ }
+ },
+
+ /**
+ * Helper function to write the preference's value to local storage
+ * and then emit a change notification.
+ */
+ _saveAndNotify: function () {
+ let store = {
+ type: this._type,
+ defaultValue: this._defaultValue,
+ hasUserValue: this._hasUserValue,
+ userValue: this._userValue,
+ };
+
+ localStorage.setItem(PREFIX + this._fullName, JSON.stringify(store));
+ this._parent._notify(this._name);
+ },
+
+ /**
+ * Change this preference's value without writing it back to local
+ * storage. This is used to handle changes to local storage that
+ * were made externally.
+ *
+ * @param {Number} type one of the PREF_* values
+ * @param {Any} userValue the user value to use if the pref does not exist
+ * @param {Any} defaultValue the default value to use if the pref
+ * does not exist
+ * @param {Boolean} hasUserValue if a new pref is created, whether
+ * the default value is also a user value
+ * @param {Object} store the new value of the preference. It should
+ * be of the form {type, defaultValue, hasUserValue, userValue};
+ * where |type| is one of the PREF_* type constants; |defaultValue|
+ * and |userValue| are the default and user values, respectively;
+ * and |hasUserValue| is a boolean indicating whether the user value
+ * is valid
+ */
+ _storageUpdated: function (type, userValue, hasUserValue, defaultValue) {
+ this._type = type;
+ this._defaultValue = defaultValue;
+ this._hasUserValue = hasUserValue;
+ this._userValue = userValue;
+ // There's no need to write this back to local storage, since it
+ // came from there; and this avoids infinite event loops.
+ this._parent._notify(this._name);
+ },
+
+ /**
+ * Helper function to find either a Preference or PrefBranch object
+ * given its name. If the name is not found, throws an exception.
+ *
+ * @param {String} prefName the fully-qualified preference name
+ * @return {Object} Either a Preference or PrefBranch object
+ */
+ _findPref: function (prefName) {
+ let branchNames = prefName.split(".");
+ let branch = this;
+
+ for (let branchName of branchNames) {
+ branch = branch._children[branchName];
+ if (!branch) {
+ throw new Error("could not find pref branch " + prefName);
+ }
+ }
+
+ return branch;
+ },
+
+ /**
+ * Helper function to notify any observers when a preference has
+ * changed. This will also notify the parent branch for further
+ * reporting.
+ *
+ * @param {String} relativeName the name of the updated pref,
+ * relative to this branch
+ */
+ _notify: function (relativeName) {
+ for (let domain in this._observers) {
+ if (relativeName === domain || domain === "" ||
+ (domain.endsWith(".") && relativeName.startsWith(domain))) {
+ // Allow mutation while walking.
+ let localList = this._observers[domain].slice();
+ for (let observer of localList) {
+ try {
+ observer.observe(this, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID,
+ relativeName);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+ }
+
+ if (this._parent) {
+ this._parent._notify(this._name + "." + relativeName);
+ }
+ },
+
+ /**
+ * Helper function to create a branch given an array of branch names
+ * representing the path of the new branch.
+ *
+ * @param {Array} branchList an array of strings, one per component
+ * of the branch to be created
+ * @return {PrefBranch} the new branch
+ */
+ _createBranch: function (branchList) {
+ let parent = this;
+ for (let branch of branchList) {
+ if (!parent._children[branch]) {
+ let isParentRoot = !parent.parent;
+ let branchName = (isParentRoot ? "" : parent.root + ".") + branch;
+ parent._children[branch] = new PrefBranch(parent, branch, branchName);
+ }
+ parent = parent._children[branch];
+ }
+ return parent;
+ },
+
+ /**
+ * Create a new preference. The new preference is assumed to be in
+ * local storage already, and the new value is taken from there.
+ *
+ * @param {String} keyName the full-qualified name of the preference.
+ * This is also the name of the key in local storage.
+ * @param {Any} userValue the user value to use if the pref does not exist
+ * @param {Boolean} hasUserValue if a new pref is created, whether
+ * the default value is also a user value
+ * @param {Any} defaultValue the default value to use if the pref
+ * does not exist
+ * @param {Boolean} init if true, then this call is initialization
+ * from local storage and should override the default prefs
+ */
+ _findOrCreatePref: function (keyName, userValue, hasUserValue, defaultValue,
+ init = false) {
+ let branch = this._createBranch(keyName.split("."));
+
+ if (hasUserValue && typeof (userValue) !== typeof (defaultValue)) {
+ throw new Error("inconsistent values when creating " + keyName);
+ }
+
+ let type;
+ switch (typeof (defaultValue)) {
+ case "boolean":
+ type = PREF_BOOL;
+ break;
+ case "number":
+ type = PREF_INT;
+ break;
+ case "string":
+ type = PREF_STRING;
+ break;
+ default:
+ throw new Error("unhandled argument type: " + typeof (defaultValue));
+ }
+
+ if (init || branch._type === PREF_INVALID) {
+ branch._storageUpdated(type, userValue, hasUserValue, defaultValue);
+ } else if (branch._type !== type) {
+ throw new Error("attempt to change type of pref " + keyName);
+ }
+
+ return branch;
+ },
+
+ /**
+ * Helper function that is called when local storage changes. This
+ * updates the preferences and notifies pref observers as needed.
+ *
+ * @param {StorageEvent} event the event representing the local
+ * storage change
+ */
+ _onStorageChange: function (event) {
+ if (event.storageArea !== localStorage) {
+ return;
+ }
+ // Ignore delete events. Not clear what's correct.
+ if (event.key === null || event.newValue === null) {
+ return;
+ }
+
+ let {type, userValue, hasUserValue, defaultValue} =
+ JSON.parse(event.newValue);
+ if (event.oldValue === null) {
+ this._findOrCreatePref(event.key, userValue, hasUserValue, defaultValue);
+ } else {
+ let thePref = this._findPref(event.key);
+ thePref._storageUpdated(type, userValue, hasUserValue, defaultValue);
+ }
+ },
+
+ /**
+ * Helper function to initialize the root PrefBranch.
+ */
+ _initializeRoot: function () {
+ if (Services._defaultPrefsEnabled) {
+ /* eslint-disable no-eval */
+ let devtools = require("raw!prefs!devtools/client/preferences/devtools");
+ eval(devtools);
+ let all = require("raw!prefs!modules/libpref/init/all");
+ eval(all);
+ /* eslint-enable no-eval */
+ }
+
+ // Read the prefs from local storage and create the local
+ // representations.
+ for (let i = 0; i < localStorage.length; ++i) {
+ let keyName = localStorage.key(i);
+ if (keyName.startsWith(PREFIX)) {
+ let {userValue, hasUserValue, defaultValue} =
+ JSON.parse(localStorage.getItem(keyName));
+ this._findOrCreatePref(keyName.slice(PREFIX.length), userValue,
+ hasUserValue, defaultValue, true);
+ }
+ }
+
+ this._onStorageChange = this._onStorageChange.bind(this);
+ window.addEventListener("storage", this._onStorageChange);
+ },
+};
+
+const Services = {
+ _prefs: null,
+
+ // For use by tests. If set to false before Services.prefs is used,
+ // this will disable the reading of the default prefs.
+ _defaultPrefsEnabled: true,
+
+ /**
+ * An implementation of nsIPrefService that is based on local
+ * storage. Only the subset of nsIPrefService that is actually used
+ * by devtools is implemented here. This is lazily instantiated so
+ * that the tests have a chance to disable the loading of default
+ * prefs.
+ */
+ get prefs() {
+ if (!this._prefs) {
+ this._prefs = new PrefBranch(null, "", "");
+ this._prefs._initializeRoot();
+ }
+ return this._prefs;
+ },
+
+ /**
+ * An implementation of Services.appinfo that holds just the
+ * properties needed by devtools.
+ */
+ appinfo: {
+ get OS() {
+ const os = window.navigator.userAgent;
+ if (os) {
+ if (os.includes("Linux")) {
+ return "Linux";
+ } else if (os.includes("Windows")) {
+ return "WINNT";
+ } else if (os.includes("Mac")) {
+ return "Darwin";
+ }
+ }
+ return "Unknown";
+ },
+
+ // It's fine for this to be an approximation.
+ get name() {
+ return window.navigator.userAgent;
+ },
+
+ // It's fine for this to be an approximation.
+ get version() {
+ return window.navigator.appVersion;
+ },
+
+ // This is only used by telemetry, which is disabled for the
+ // content case. So, being totally wrong is ok.
+ get is64Bit() {
+ return true;
+ },
+ },
+
+ /**
+ * A no-op implementation of Services.telemetry. This supports just
+ * the subset of Services.telemetry that is used by devtools.
+ */
+ telemetry: {
+ getHistogramById: function (name) {
+ return {
+ add: () => {}
+ };
+ },
+
+ getKeyedHistogramById: function (name) {
+ return {
+ add: () => {}
+ };
+ },
+ },
+
+ /**
+ * An implementation of Services.focus that holds just the
+ * properties and methods needed by devtools.
+ * @see nsIFocusManager.idl for details.
+ */
+ focus: {
+ // These values match nsIFocusManager in order to make testing a
+ // bit simpler.
+ MOVEFOCUS_FORWARD: 1,
+ MOVEFOCUS_BACKWARD: 2,
+
+ get focusedElement() {
+ if (!document.hasFocus()) {
+ return null;
+ }
+ return document.activeElement;
+ },
+
+ moveFocus: function (window, startElement, type, flags) {
+ if (flags !== 0) {
+ throw new Error("shim Services.focus.moveFocus only accepts flags===0");
+ }
+ if (type !== Services.focus.MOVEFOCUS_FORWARD
+ && type !== Services.focus.MOVEFOCUS_BACKWARD) {
+ throw new Error("shim Services.focus.moveFocus only supports " +
+ " MOVEFOCUS_FORWARD and MOVEFOCUS_BACKWARD");
+ }
+
+ if (!startElement) {
+ startElement = document.activeElement || document;
+ }
+
+ let iter = document.createTreeWalker(document, NodeFilter.SHOW_ELEMENT, {
+ acceptNode: function (node) {
+ let tabIndex = node.getAttribute("tabindex");
+ if (tabIndex === "-1") {
+ return NodeFilter.FILTER_SKIP;
+ }
+ node.focus();
+ if (document.activeElement == node) {
+ return NodeFilter.FILTER_ACCEPT;
+ }
+ return NodeFilter.FILTER_SKIP;
+ }
+ });
+
+ iter.currentNode = startElement;
+
+ // Sets the focus via side effect in the filter.
+ if (type === Services.focus.MOVEFOCUS_FORWARD) {
+ iter.nextNode();
+ } else {
+ iter.previousNode();
+ }
+ },
+ },
+
+ /**
+ * An implementation of Services.wm that provides a shim for
+ * getMostRecentWindow.
+ */
+ wm: {
+ getMostRecentWindow: function () {
+ // Having the returned object implement openUILinkIn is
+ // sufficient for our purposes.
+ return {
+ openUILinkIn: function (url) {
+ window.open(url, "_blank");
+ },
+ };
+ },
+ },
+};
+
+/**
+ * Create a new preference. This is used during startup (see
+ * devtools/client/preferences/devtools.js) to install the
+ * default preferences.
+ *
+ * @param {String} name the name of the preference
+ * @param {Any} value the default value of the preference
+ */
+function pref(name, value) {
+ let thePref = Services.prefs._findOrCreatePref(name, value, true, value);
+ thePref._setDefault(value);
+}
+
+module.exports = Services;
+// This is exported to silence eslint.
+exports.pref = pref;
diff --git a/devtools/client/shared/shim/moz.build b/devtools/client/shared/shim/moz.build
new file mode 100644
index 000000000..dff3e903c
--- /dev/null
+++ b/devtools/client/shared/shim/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+ 'Services.js',
+)
+
+MOCHITEST_MANIFESTS += [
+ 'test/mochitest.ini',
+]
diff --git a/devtools/client/shared/shim/test/.eslintrc.js b/devtools/client/shared/shim/test/.eslintrc.js
new file mode 100644
index 000000000..698ae9181
--- /dev/null
+++ b/devtools/client/shared/shim/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/client/shared/shim/test/file_service_wm.html b/devtools/client/shared/shim/test/file_service_wm.html
new file mode 100644
index 000000000..24753e710
--- /dev/null
+++ b/devtools/client/shared/shim/test/file_service_wm.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script>
+// Silence eslint complaint about the onload below.
+/* eslint-disable no-unused-vars */
+
+"use strict";
+
+function load() {
+ (window.opener || window.parent).hurray();
+ window.close();
+}
+</script>
+</head>
+
+<body onload='load()'>
+</body>
+
+</html>
diff --git a/devtools/client/shared/shim/test/mochitest.ini b/devtools/client/shared/shim/test/mochitest.ini
new file mode 100644
index 000000000..4ba5cd6c1
--- /dev/null
+++ b/devtools/client/shared/shim/test/mochitest.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files =
+ file_service_wm.html
+ prefs-wrapper.js
+
+[test_service_appinfo.html]
+[test_service_focus.html]
+[test_service_prefs.html]
+[test_service_prefs_defaults.html]
+[test_service_wm.html]
diff --git a/devtools/client/shared/shim/test/prefs-wrapper.js b/devtools/client/shared/shim/test/prefs-wrapper.js
new file mode 100644
index 000000000..057e12d17
--- /dev/null
+++ b/devtools/client/shared/shim/test/prefs-wrapper.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// A wrapper for Services.prefs that compares our shim content
+// implementation with the real service.
+
+// We assume we're loaded in a global where Services was already loaded.
+/* globals isDeeply, Services */
+
+"use strict";
+
+function setMethod(methodName, prefName, value) {
+ let savedException;
+ let prefThrew = false;
+ try {
+ Services.prefs[methodName](prefName, value);
+ } catch (e) {
+ prefThrew = true;
+ savedException = e;
+ }
+
+ let realThrew = false;
+ try {
+ SpecialPowers[methodName](prefName, value);
+ } catch (e) {
+ realThrew = true;
+ savedException = e;
+ }
+
+ is(prefThrew, realThrew, methodName + " [throw check]");
+ if (prefThrew || realThrew) {
+ throw savedException;
+ }
+}
+
+function getMethod(methodName, prefName) {
+ let prefThrew = false;
+ let prefValue = undefined;
+ let savedException;
+ try {
+ prefValue = Services.prefs[methodName](prefName);
+ } catch (e) {
+ prefThrew = true;
+ savedException = e;
+ }
+
+ let realValue = undefined;
+ let realThrew = false;
+ try {
+ realValue = SpecialPowers[methodName](prefName);
+ } catch (e) {
+ realThrew = true;
+ savedException = e;
+ }
+
+ is(prefThrew, realThrew, methodName + " [throw check]");
+ isDeeply(prefValue, realValue, methodName + " [equality]");
+ if (prefThrew || realThrew) {
+ throw savedException;
+ }
+
+ return prefValue;
+}
+
+var WrappedPrefs = {};
+
+for (let method of ["getPrefType", "getBoolPref", "getCharPref", "getIntPref",
+ "clearUserPref"]) {
+ WrappedPrefs[method] = getMethod.bind(null, method);
+}
+
+for (let method of ["setBoolPref", "setCharPref", "setIntPref"]) {
+ WrappedPrefs[method] = setMethod.bind(null, method);
+}
+
+// Silence eslint.
+exports.WrappedPrefs = WrappedPrefs;
diff --git a/devtools/client/shared/shim/test/test_service_appinfo.html b/devtools/client/shared/shim/test/test_service_appinfo.html
new file mode 100644
index 000000000..bc659cb42
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_appinfo.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265802
+-->
+<head>
+ <title>Test for Bug 1265802 - replace Services.appinfo</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript;version=1.8">
+ "use strict";
+ var exports = {}
+ var module = {exports};
+ </script>
+
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+is(Services.appinfo.OS, SpecialPowers.Services.appinfo.OS,
+ "check that Services.appinfo.OS shim matches platform");
+</script>
+</body>
diff --git a/devtools/client/shared/shim/test/test_service_focus.html b/devtools/client/shared/shim/test/test_service_focus.html
new file mode 100644
index 000000000..d720e0b53
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_focus.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1278473
+-->
+<head>
+ <title>Test for Bug 1278473 - replace Services.focus</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript;version=1.8">
+ "use strict";
+ var exports = {}
+ var module = {exports};
+ </script>
+
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+ <span>
+ <span id="start" testvalue="0" tabindex="0"> </span>
+ <label>
+ <input testvalue="1" type="radio">Hi</input>
+ </label>
+ <label>
+ <input type="radio" tabindex="-1">Bye</input>
+ </label>
+ <label style="display: none">
+ <input id="button3" type="radio" tabindex="-1">Invisible</input>
+ </label>
+ <input id="button4" type="radio" disabled="true">Disabled</input>
+ <span testvalue="2" tabindex="0"> </span>
+ </span>
+
+<script type="application/javascript;version=1.8">
+ "use strict";
+
+ // The test assumes these are identical, so assert it here.
+ is(Services.focus.MOVEFOCUS_BACKWARD, SpecialPowers.Services.focus.MOVEFOCUS_BACKWARD,
+ "check MOVEFOCUS_BACKWARD");
+ is(Services.focus.MOVEFOCUS_FORWARD, SpecialPowers.Services.focus.MOVEFOCUS_FORWARD,
+ "check MOVEFOCUS_FORWARD");
+
+ function moveFocus(element, type, expect) {
+ let current = document.activeElement;
+ const suffix = "(type=" + type + ", to=" + expect + ")";
+
+ // First try with the platform implementation.
+ SpecialPowers.Services.focus.moveFocus(window, element, type, 0);
+ is(document.activeElement.getAttribute("testvalue"), expect,
+ "platform moveFocus " + suffix);
+
+ // Reset the focus and try again with the shim.
+ current.focus();
+ is(document.activeElement, current, "reset " + suffix);
+
+ Services.focus.moveFocus(window, element, type, 0);
+ is(document.activeElement.getAttribute("testvalue"), expect,
+ "shim moveFocus " + suffix);
+ }
+
+ let start = document.querySelector("#start");
+ start.focus();
+ is(document.activeElement.getAttribute("testvalue"), "0", "initial focus");
+
+ moveFocus(null, Services.focus.MOVEFOCUS_FORWARD, "1");
+ moveFocus(null, Services.focus.MOVEFOCUS_FORWARD, "2");
+ let end = document.activeElement;
+ moveFocus(null, Services.focus.MOVEFOCUS_BACKWARD, "1");
+ moveFocus(null, Services.focus.MOVEFOCUS_BACKWARD, "0");
+
+ moveFocus(start, Services.focus.MOVEFOCUS_FORWARD, "1");
+ moveFocus(end, Services.focus.MOVEFOCUS_BACKWARD, "1");
+</script>
+</body>
diff --git a/devtools/client/shared/shim/test/test_service_prefs.html b/devtools/client/shared/shim/test/test_service_prefs.html
new file mode 100644
index 000000000..99e827dfd
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_prefs.html
@@ -0,0 +1,244 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1265808
+-->
+<head>
+ <title>Test for Bug 1265808 - replace Services.prefs</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+<script type="application/javascript;version=1.8">
+"use strict";
+var exports = {}
+var module = {exports};
+
+ // Add some starter prefs.
+localStorage.setItem("Services.prefs:devtools.branch1.somebool", JSON.stringify({
+ // bool
+ type: 128,
+ defaultValue: false,
+ hasUserValue: false,
+ userValue: false
+}));
+
+localStorage.setItem("Services.prefs:devtools.branch1.somestring", JSON.stringify({
+ // string
+ type: 32,
+ defaultValue: "dinosaurs",
+ hasUserValue: true,
+ userValue: "elephants"
+}));
+
+localStorage.setItem("Services.prefs:devtools.branch2.someint", JSON.stringify({
+ // string
+ type: 64,
+ defaultValue: -16,
+ hasUserValue: false,
+ userValue: null
+}));
+
+</script>
+
+ <script type="application/javascript;version=1.8"
+ src="prefs-wrapper.js"></script>
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+function do_tests() {
+ // We can't load the defaults in this context.
+ Services._defaultPrefsEnabled = false;
+
+ is(Services.prefs.getBoolPref("devtools.branch1.somebool"), false,
+ "bool pref value");
+ Services.prefs.setBoolPref("devtools.branch1.somebool", true);
+ is(Services.prefs.getBoolPref("devtools.branch1.somebool"), true,
+ "bool pref value after setting");
+
+ let threw;
+
+ try {
+ threw = false;
+ WrappedPrefs.getIntPref("devtools.branch1.somebool");
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "type-checking for bool pref");
+
+ try {
+ threw = false;
+ Services.prefs.setIntPref("devtools.branch1.somebool", 27);
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "type-checking for setting bool pref");
+
+ try {
+ threw = false;
+ Services.prefs.setBoolPref("devtools.branch1.somebool", 27);
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "setting bool pref to wrong type");
+
+ try {
+ threw = false;
+ Services.prefs.getCharPref("devtools.branch2.someint");
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "type-checking for int pref");
+
+ try {
+ threw = false;
+ Services.prefs.setCharPref("devtools.branch2.someint", "whatever");
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "type-checking for setting int pref");
+
+ try {
+ threw = false;
+ Services.prefs.setIntPref("devtools.branch2.someint", "whatever");
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "setting int pref to wrong type");
+
+ try {
+ threw = false;
+ Services.prefs.getBoolPref("devtools.branch1.somestring");
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "type-checking for char pref");
+
+ try {
+ threw = false;
+ Services.prefs.setBoolPref("devtools.branch1.somestring", true);
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "type-checking for setting char pref");
+
+ try {
+ threw = false;
+ Services.prefs.setCharPref("devtools.branch1.somestring", true);
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "setting char pref to wrong type");
+
+ is(Services.prefs.getPrefType("devtools.branch1.somebool"),
+ Services.prefs.PREF_BOOL, "type of bool pref");
+ is(Services.prefs.getPrefType("devtools.branch2.someint"),
+ Services.prefs.PREF_INT, "type of int pref");
+ is(Services.prefs.getPrefType("devtools.branch1.somestring"),
+ Services.prefs.PREF_STRING, "type of string pref");
+
+ WrappedPrefs.setBoolPref("devtools.branch1.somebool", true);
+ ok(WrappedPrefs.getBoolPref("devtools.branch1.somebool"), "set bool pref");
+ WrappedPrefs.setIntPref("devtools.branch2.someint", -93);
+ is(WrappedPrefs.getIntPref("devtools.branch2.someint"), -93, "set int pref");
+ WrappedPrefs.setCharPref("devtools.branch1.somestring", "hello");
+ is(WrappedPrefs.getCharPref("devtools.branch1.somestring"), "hello",
+ "set string pref");
+
+ Services.prefs.clearUserPref("devtools.branch1.somestring");
+ is(Services.prefs.getCharPref("devtools.branch1.somestring"), "dinosaurs",
+ "clear string pref");
+
+ ok(Services.prefs.prefHasUserValue("devtools.branch1.somebool"),
+ "bool pref has user value");
+ ok(!Services.prefs.prefHasUserValue("devtools.branch1.somestring"),
+ "string pref does not have user value");
+
+
+ Services.prefs.savePrefFile(null);
+ ok(true, "saved pref file without error");
+
+
+ let branch0 = Services.prefs.getBranch(null);
+ let branch1 = Services.prefs.getBranch("devtools.branch1.");
+
+ branch1.setCharPref("somestring", "octopus");
+ Services.prefs.setCharPref("devtools.branch1.somestring", "octopus");
+ is(Services.prefs.getCharPref("devtools.branch1.somestring"), "octopus",
+ "set correctly via branch");
+ is(branch0.getCharPref("devtools.branch1.somestring"), "octopus",
+ "get via base branch");
+ is(branch1.getCharPref("somestring"), "octopus", "get via branch");
+
+
+ let notifications = {};
+ let clearNotificationList = () => { notifications = {}; }
+ let observer = {
+ observe: function (subject, topic, data) {
+ notifications[data] = true;
+ }
+ };
+
+ branch0.addObserver("devtools.branch1", null, null);
+ branch0.addObserver("devtools.branch1.", observer, false);
+ branch1.addObserver("", observer, false);
+
+ Services.prefs.setCharPref("devtools.branch1.somestring", "elf owl");
+ isDeeply(notifications, {
+ "devtools.branch1.somestring": true,
+ "somestring": true
+ }, "notifications sent to two listeners");
+
+ clearNotificationList();
+ Services.prefs.setIntPref("devtools.branch2.someint", 1729);
+ isDeeply(notifications, {}, "no notifications sent");
+
+ clearNotificationList();
+ branch0.removeObserver("devtools.branch1.", observer);
+ Services.prefs.setCharPref("devtools.branch1.somestring", "tapir");
+ isDeeply(notifications, {
+ "somestring": true
+ }, "removeObserver worked");
+
+ clearNotificationList();
+ branch0.addObserver("devtools.branch1.somestring", observer, false);
+ Services.prefs.setCharPref("devtools.branch1.somestring", "northern shoveler");
+ isDeeply(notifications, {
+ "devtools.branch1.somestring": true,
+ "somestring": true
+ }, "notifications sent to two listeners");
+ branch0.removeObserver("devtools.branch1.somestring", observer);
+
+ // Make sure we update if the pref change comes from somewhere else.
+ clearNotificationList();
+ pref("devtools.branch1.someotherstring", "lazuli bunting");
+ isDeeply(notifications, {
+ "someotherstring": true
+ }, "pref worked");
+
+ // Regression test for bug 1296427.
+ pref("devtools.hud.loglimit", 1000);
+ pref("devtools.hud.loglimit.network", 1000);
+
+ // Clean up.
+ localStorage.clear();
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv(
+ {"set": [
+ ["devtools.branch1.somestring", "elephants"],
+ ["devtools.branch1.somebool", false],
+ ["devtools.branch2.someint", "-16"],
+ ]},
+ do_tests);
+
+</script>
+</body>
diff --git a/devtools/client/shared/shim/test/test_service_prefs_defaults.html b/devtools/client/shared/shim/test/test_service_prefs_defaults.html
new file mode 100644
index 000000000..d8933b74a
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_prefs_defaults.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1309384
+-->
+<head>
+ <title>Test for Bug 1309384 - Services.prefs replacement defaults handling</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+<script type="application/javascript;version=1.8">
+"use strict";
+var exports = {}
+var module = {exports};
+
+// Allow one require("raw!prefs...") to return some defaults, with the
+// others being ignored.
+var firstTime = true;
+function require(something) {
+ if (!something.startsWith("raw!prefs!")) {
+ throw new Error("whoops");
+ }
+ if (!firstTime) {
+ return "";
+ }
+ firstTime = false;
+ return "pref('pref1', 'pref1default');\n" +
+ "pref('pref2', 'pref2default');\n" +
+ "pref('pref3', 'pref3default');\n";
+}
+
+// Pretend that one of the prefs was modifed by the user in an earlier session.
+localStorage.setItem("Services.prefs:pref3", JSON.stringify({
+ // string
+ type: 32,
+ defaultValue: "pref3default",
+ hasUserValue: true,
+ userValue: "glass winged butterfly"
+}));
+
+</script>
+
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+is(Services.prefs.getCharPref("pref1"), "pref1default", "pref1 value");
+is(Services.prefs.getCharPref("pref2"), "pref2default", "pref2 value");
+is(Services.prefs.getCharPref("pref3"), "glass winged butterfly", "pref3 value");
+
+// Only pref3 should be in local storage at this point.
+is(localStorage.length, 1, "local storage is correct");
+
+Services.prefs.setCharPref("pref2", "pref2override");
+
+// Check that a default pref can be overridden properly
+
+// Workaround to reset the prefs helper and force it to read defaults & overrides again.
+Services._prefs = null;
+is(Services.prefs.getCharPref("pref2"), "pref2override", "pref2 value overridden");
+
+// Clean up.
+localStorage.clear();
+
+</script>
+</body>
diff --git a/devtools/client/shared/shim/test/test_service_wm.html b/devtools/client/shared/shim/test/test_service_wm.html
new file mode 100644
index 000000000..4db602f7e
--- /dev/null
+++ b/devtools/client/shared/shim/test/test_service_wm.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1310279
+-->
+<head>
+ <title>Test for Bug 1310279 - replace Services.wm</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript;version=1.8">
+ "use strict";
+ var exports = {}
+ var module = {exports};
+ </script>
+
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/client/shared/shim/Services.js"></script>
+</head>
+<body>
+
+<script type="application/javascript;version=1.8">
+ "use strict";
+
+ function hurray(window) {
+ ok(true, "window loaded");
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ Services.wm.getMostRecentWindow().openUILinkIn("file_service_wm.html");
+
+</script>
+</body>