diff options
Diffstat (limited to 'devtools/client/shared/shim')
-rw-r--r-- | devtools/client/shared/shim/Services.js | 620 | ||||
-rw-r--r-- | devtools/client/shared/shim/moz.build | 13 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/.eslintrc.js | 6 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/file_service_wm.html | 20 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/mochitest.ini | 10 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/prefs-wrapper.js | 80 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/test_service_appinfo.html | 29 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/test_service_focus.html | 78 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/test_service_prefs.html | 244 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/test_service_prefs_defaults.html | 71 | ||||
-rw-r--r-- | devtools/client/shared/shim/test/test_service_wm.html | 36 |
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> |