зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to central, a=merge
MozReview-Commit-ID: 6kqXEAQXEQI
This commit is contained in:
Коммит
d946d2f1e5
|
@ -112,7 +112,6 @@ DEFAULT_FIREFOX_PREFS = {
|
|||
'browser.startup.homepage' : 'about:blank',
|
||||
'startup.homepage_welcome_url' : 'about:blank',
|
||||
'devtools.browsertoolbox.panel': 'jsdebugger',
|
||||
'devtools.errorconsole.enabled' : True,
|
||||
'devtools.chrome.enabled' : True,
|
||||
|
||||
# From:
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"browser.startup.homepage": "about:blank",
|
||||
"startup.homepage_welcome_url": "about:blank",
|
||||
"devtools.browsertoolbox.panel": "jsdebugger",
|
||||
"devtools.errorconsole.enabled": true,
|
||||
"devtools.chrome.enabled": true,
|
||||
"urlclassifier.updateinterval": 172800,
|
||||
"browser.safebrowsing.provider.google.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
|
|
|
@ -157,12 +157,6 @@ XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
|
|||
}
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
|
||||
let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar");
|
||||
return new DeveloperToolbar(window);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
|
||||
let tmp = {};
|
||||
Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp);
|
||||
|
@ -1400,11 +1394,6 @@ var gBrowserInit = {
|
|||
if (!this._loadHandled)
|
||||
return;
|
||||
|
||||
let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
|
||||
if (desc && !desc.get) {
|
||||
DeveloperToolbar.destroy();
|
||||
}
|
||||
|
||||
// First clean up services initialized in gBrowserInit.onLoad (or those whose
|
||||
// uninit methods don't depend on the services having been initialized).
|
||||
|
||||
|
|
|
@ -130,6 +130,11 @@ function handleGUMRequest(aSubject, aTopic, aData) {
|
|||
contentWindow.navigator.mozGetUserMediaDevices(
|
||||
constraints,
|
||||
function (devices) {
|
||||
// If the window has been closed while we were waiting for the list of
|
||||
// devices, there's nothing to do in the callback anymore.
|
||||
if (contentWindow.closed)
|
||||
return;
|
||||
|
||||
prompt(contentWindow, aSubject.windowID, aSubject.callID,
|
||||
constraints, devices, secure);
|
||||
},
|
||||
|
|
|
@ -112,24 +112,6 @@ function reload(event) {
|
|||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
// Manually reload gcli if it has been used
|
||||
// Bug 1248348: Inject the developer toolbar dynamically within browser/
|
||||
// so that we can easily remove/reinject it
|
||||
const desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
|
||||
if (desc && !desc.get) {
|
||||
let wasVisible = window.DeveloperToolbar.visible;
|
||||
window.DeveloperToolbar.hide()
|
||||
.then(() => {
|
||||
window.DeveloperToolbar.destroy();
|
||||
|
||||
let { DeveloperToolbar } = devtools.require("devtools/client/shared/developer-toolbar");
|
||||
window.DeveloperToolbar = new DeveloperToolbar(window, window.document.getElementById("developer-toolbar"));
|
||||
if (wasVisible) {
|
||||
window.DeveloperToolbar.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (windowtype === "devtools:webide") {
|
||||
window.location.reload();
|
||||
} else if (windowtype === "devtools:webconsole") {
|
||||
|
|
|
@ -80,14 +80,12 @@ AnimationDetails.prototype = {
|
|||
*/
|
||||
if (this.serverTraits.hasGetProperties) {
|
||||
let properties = yield this.animation.getProperties();
|
||||
for (let propertyObject of properties) {
|
||||
let name = propertyObject.property;
|
||||
|
||||
for (let {name, values} of properties) {
|
||||
if (!tracks[name]) {
|
||||
tracks[name] = [];
|
||||
}
|
||||
|
||||
for (let {value, offset} of propertyObject.values) {
|
||||
for (let {value, offset} of values) {
|
||||
tracks[name].push({value, offset});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,15 +60,12 @@ function* getExpectedKeyframesData(animation) {
|
|||
|
||||
for (let expectedProperty of EXPECTED_PROPERTIES) {
|
||||
data[expectedProperty] = [];
|
||||
for (let propertyObject of properties) {
|
||||
if (propertyObject.property !== expectedProperty) {
|
||||
for (let {name, values} of properties) {
|
||||
if (name !== expectedProperty) {
|
||||
continue;
|
||||
}
|
||||
for (let valueObject of propertyObject.values) {
|
||||
data[expectedProperty].push({
|
||||
offset: valueObject.offset,
|
||||
value: valueObject.value
|
||||
});
|
||||
for (let {offset, value} of values) {
|
||||
data[expectedProperty].push({offset, value});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ const Services = require("Services");
|
|||
const MenuStrings = Services.strings.createBundle("chrome://devtools/locale/menus.properties");
|
||||
|
||||
loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
|
||||
loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
|
||||
|
||||
// Keep list of inserted DOM Elements in order to remove them on unload
|
||||
// Maps browser xul document => list of DOM Elements
|
||||
|
@ -30,40 +31,41 @@ function l10n(key) {
|
|||
*
|
||||
* @param {XULDocument} doc
|
||||
* The document to which keys are to be added.
|
||||
* @param {String} l10nKey
|
||||
* Prefix of the properties entry to look for key shortcut in
|
||||
* localization file. We will look for {property}.key and
|
||||
* {property}.keytext for non-character shortcuts like F12.
|
||||
* @param {String} command
|
||||
* Id of the xul:command to map to.
|
||||
* @param {Object} key definition dictionnary
|
||||
* Definition with following attributes:
|
||||
* - {String} id
|
||||
* xul:key's id, automatically prefixed with "key_",
|
||||
* - {String} modifiers
|
||||
* Space separater list of modifier names,
|
||||
* - {Boolean} keytext
|
||||
* If true, consider the shortcut as a characther one,
|
||||
* otherwise a non-character one like F12.
|
||||
* @param {String} id
|
||||
* key's id, automatically prefixed with "key_".
|
||||
* @param {String} shortcut
|
||||
* The key shortcut value.
|
||||
* @param {String} keytext
|
||||
* If `shortcut` refers to a function key, refers to the localized
|
||||
* string to describe a non-character shortcut.
|
||||
* @param {String} modifiers
|
||||
* Space separated list of modifier names.
|
||||
* @param {Function} oncommand
|
||||
* The function to call when the shortcut is pressed.
|
||||
*
|
||||
* @return XULKeyElement
|
||||
*/
|
||||
function createKey(doc, l10nKey, command, key) {
|
||||
function createKey({ doc, id, shortcut, keytext, modifiers, oncommand }) {
|
||||
let k = doc.createElement("key");
|
||||
k.id = "key_" + key.id;
|
||||
let shortcut = l10n(l10nKey + ".key");
|
||||
k.id = "key_" + id;
|
||||
|
||||
if (shortcut.startsWith("VK_")) {
|
||||
k.setAttribute("keycode", shortcut);
|
||||
k.setAttribute("keytext", l10n(l10nKey + ".keytext"));
|
||||
if (keytext) {
|
||||
k.setAttribute("keytext", keytext);
|
||||
}
|
||||
} else {
|
||||
k.setAttribute("key", shortcut);
|
||||
}
|
||||
if (command) {
|
||||
k.setAttribute("command", command);
|
||||
}
|
||||
if (key.modifiers) {
|
||||
k.setAttribute("modifiers", key.modifiers);
|
||||
|
||||
if (modifiers) {
|
||||
k.setAttribute("modifiers", modifiers);
|
||||
}
|
||||
|
||||
// Bug 371900: command event is fired only if "oncommand" attribute is set.
|
||||
k.setAttribute("oncommand", ";");
|
||||
k.addEventListener("command", oncommand);
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
|
@ -76,25 +78,18 @@ function createKey(doc, l10nKey, command, key) {
|
|||
* Element id.
|
||||
* @param {String} label
|
||||
* Menu label.
|
||||
* @param {String} broadcasterId (optional)
|
||||
* Id of the xul:broadcaster to map to.
|
||||
* @param {String} accesskey (optional)
|
||||
* Access key of the menuitem, used as shortcut while opening the menu.
|
||||
* @param {Boolean} isCheckbox
|
||||
* @param {Boolean} isCheckbox (optional)
|
||||
* If true, the menuitem will act as a checkbox and have an optional
|
||||
* tick on its left.
|
||||
*
|
||||
* @return XULMenuItemElement
|
||||
*/
|
||||
function createMenuItem({ doc, id, label, broadcasterId, accesskey, isCheckbox }) {
|
||||
function createMenuItem({ doc, id, label, accesskey, isCheckbox }) {
|
||||
let menuitem = doc.createElement("menuitem");
|
||||
menuitem.id = id;
|
||||
if (label) {
|
||||
menuitem.setAttribute("label", label);
|
||||
}
|
||||
if (broadcasterId) {
|
||||
menuitem.setAttribute("observes", broadcasterId);
|
||||
}
|
||||
menuitem.setAttribute("label", label);
|
||||
if (accesskey) {
|
||||
menuitem.setAttribute("accesskey", accesskey);
|
||||
}
|
||||
|
@ -105,56 +100,6 @@ function createMenuItem({ doc, id, label, broadcasterId, accesskey, isCheckbox }
|
|||
return menuitem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a xul:broadcaster element
|
||||
*
|
||||
* @param {XULDocument} doc
|
||||
* The document to which keys are to be added.
|
||||
* @param {String} id
|
||||
* Element id.
|
||||
* @param {String} label
|
||||
* Broadcaster label.
|
||||
* @param {Boolean} isCheckbox
|
||||
* If true, the broadcaster is a checkbox one.
|
||||
*
|
||||
* @return XULMenuItemElement
|
||||
*/
|
||||
function createBroadcaster({ doc, id, label, isCheckbox }) {
|
||||
let broadcaster = doc.createElement("broadcaster");
|
||||
broadcaster.id = id;
|
||||
broadcaster.setAttribute("label", label);
|
||||
if (isCheckbox) {
|
||||
broadcaster.setAttribute("type", "checkbox");
|
||||
broadcaster.setAttribute("autocheck", "false");
|
||||
}
|
||||
return broadcaster;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a xul:command element
|
||||
*
|
||||
* @param {XULDocument} doc
|
||||
* The document to which keys are to be added.
|
||||
* @param {String} id
|
||||
* Element id.
|
||||
* @param {String} oncommand
|
||||
* JS String to run when the command is fired.
|
||||
* @param {Boolean} disabled
|
||||
* If true, the command is disabled and hidden.
|
||||
*
|
||||
* @return XULCommandElement
|
||||
*/
|
||||
function createCommand({ doc, id, oncommand, disabled }) {
|
||||
let command = doc.createElement("command");
|
||||
command.id = id;
|
||||
command.setAttribute("oncommand", oncommand);
|
||||
if (disabled) {
|
||||
command.setAttribute("disabled", "true");
|
||||
command.setAttribute("hidden", "true");
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a <key> to <keyset id="devtoolsKeyset">.
|
||||
* Appending a <key> element is not always enough. The <keyset> needs
|
||||
|
@ -188,61 +133,49 @@ function attachKeybindingsToBrowser(doc, keys) {
|
|||
*/
|
||||
function createToolMenuElements(toolDefinition, doc) {
|
||||
let id = toolDefinition.id;
|
||||
let menuId = "menuitem_" + id;
|
||||
|
||||
// Prevent multiple entries for the same tool.
|
||||
if (doc.getElementById("Tools:" + id)) {
|
||||
if (doc.getElementById(menuId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cmd = createCommand({
|
||||
doc,
|
||||
id: "Tools:" + id,
|
||||
oncommand: 'gDevToolsBrowser.selectToolCommand(gBrowser, "' + id + '");',
|
||||
});
|
||||
let oncommand = function (id, event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
gDevToolsBrowser.selectToolCommand(window.gBrowser, id);
|
||||
}.bind(null, id);
|
||||
|
||||
let key = null;
|
||||
if (toolDefinition.key) {
|
||||
key = doc.createElement("key");
|
||||
key.id = "key_" + id;
|
||||
|
||||
if (toolDefinition.key.startsWith("VK_")) {
|
||||
key.setAttribute("keycode", toolDefinition.key);
|
||||
} else {
|
||||
key.setAttribute("key", toolDefinition.key);
|
||||
}
|
||||
|
||||
key.setAttribute("command", cmd.id);
|
||||
key.setAttribute("modifiers", toolDefinition.modifiers);
|
||||
}
|
||||
|
||||
let bc = createBroadcaster({
|
||||
doc,
|
||||
id: "devtoolsMenuBroadcaster_" + id,
|
||||
label: toolDefinition.menuLabel || toolDefinition.label
|
||||
});
|
||||
bc.setAttribute("command", cmd.id);
|
||||
|
||||
if (key) {
|
||||
bc.setAttribute("key", "key_" + id);
|
||||
key = createKey({
|
||||
doc,
|
||||
id,
|
||||
shortcut: toolDefinition.key,
|
||||
modifiers: toolDefinition.modifiers,
|
||||
oncommand: oncommand
|
||||
});
|
||||
}
|
||||
|
||||
let menuitem = createMenuItem({
|
||||
doc,
|
||||
id: "menuitem_" + id,
|
||||
broadcasterId: "devtoolsMenuBroadcaster_" + id,
|
||||
label: toolDefinition.menuLabel || toolDefinition.label,
|
||||
accesskey: toolDefinition.accesskey
|
||||
});
|
||||
if (key) {
|
||||
// Refer to the key in order to display the key shortcut at menu ends
|
||||
menuitem.setAttribute("key", key.id);
|
||||
}
|
||||
menuitem.addEventListener("command", oncommand);
|
||||
|
||||
return {
|
||||
cmd: cmd,
|
||||
key: key,
|
||||
bc: bc,
|
||||
menuitem: menuitem
|
||||
key,
|
||||
menuitem
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create xul menuitem, command, broadcaster and key elements for a given tool.
|
||||
* Create xul menuitem, key elements for a given tool.
|
||||
* And then insert them into browser DOM.
|
||||
*
|
||||
* @param {XULDocument} doc
|
||||
|
@ -253,16 +186,12 @@ function createToolMenuElements(toolDefinition, doc) {
|
|||
* The tool definition after which the tool menu item is to be added.
|
||||
*/
|
||||
function insertToolMenuElements(doc, toolDefinition, prevDef) {
|
||||
let elements = createToolMenuElements(toolDefinition, doc);
|
||||
let { key, menuitem } = createToolMenuElements(toolDefinition, doc);
|
||||
|
||||
doc.getElementById("mainCommandSet").appendChild(elements.cmd);
|
||||
|
||||
if (elements.key) {
|
||||
attachKeybindingsToBrowser(doc, elements.key);
|
||||
if (key) {
|
||||
attachKeybindingsToBrowser(doc, key);
|
||||
}
|
||||
|
||||
doc.getElementById("mainBroadcasterSet").appendChild(elements.bc);
|
||||
|
||||
let ref;
|
||||
if (prevDef) {
|
||||
let menuitem = doc.getElementById("menuitem_" + prevDef.id);
|
||||
|
@ -272,7 +201,7 @@ function insertToolMenuElements(doc, toolDefinition, prevDef) {
|
|||
}
|
||||
|
||||
if (ref) {
|
||||
ref.parentNode.insertBefore(elements.menuitem, ref);
|
||||
ref.parentNode.insertBefore(menuitem, ref);
|
||||
}
|
||||
}
|
||||
exports.insertToolMenuElements = insertToolMenuElements;
|
||||
|
@ -286,24 +215,14 @@ exports.insertToolMenuElements = insertToolMenuElements;
|
|||
* The document to which the tool menu item is to be removed from
|
||||
*/
|
||||
function removeToolFromMenu(toolId, doc) {
|
||||
let command = doc.getElementById("Tools:" + toolId);
|
||||
if (command) {
|
||||
command.parentNode.removeChild(command);
|
||||
}
|
||||
|
||||
let key = doc.getElementById("key_" + toolId);
|
||||
if (key) {
|
||||
key.parentNode.removeChild(key);
|
||||
}
|
||||
|
||||
let bc = doc.getElementById("devtoolsMenuBroadcaster_" + toolId);
|
||||
if (bc) {
|
||||
bc.parentNode.removeChild(bc);
|
||||
key.remove();
|
||||
}
|
||||
|
||||
let menuitem = doc.getElementById("menuitem_" + toolId);
|
||||
if (menuitem) {
|
||||
menuitem.parentNode.removeChild(menuitem);
|
||||
menuitem.remove();
|
||||
}
|
||||
}
|
||||
exports.removeToolFromMenu = removeToolFromMenu;
|
||||
|
@ -315,9 +234,7 @@ exports.removeToolFromMenu = removeToolFromMenu;
|
|||
* The document to which the tool items are to be added.
|
||||
*/
|
||||
function addAllToolsToMenu(doc) {
|
||||
let fragCommands = doc.createDocumentFragment();
|
||||
let fragKeys = doc.createDocumentFragment();
|
||||
let fragBroadcasters = doc.createDocumentFragment();
|
||||
let fragMenuItems = doc.createDocumentFragment();
|
||||
|
||||
for (let toolDefinition of gDevTools.getToolDefinitionArray()) {
|
||||
|
@ -331,22 +248,14 @@ function addAllToolsToMenu(doc) {
|
|||
continue;
|
||||
}
|
||||
|
||||
fragCommands.appendChild(elements.cmd);
|
||||
if (elements.key) {
|
||||
fragKeys.appendChild(elements.key);
|
||||
}
|
||||
fragBroadcasters.appendChild(elements.bc);
|
||||
fragMenuItems.appendChild(elements.menuitem);
|
||||
}
|
||||
|
||||
let mcs = doc.getElementById("mainCommandSet");
|
||||
mcs.appendChild(fragCommands);
|
||||
|
||||
attachKeybindingsToBrowser(doc, fragKeys);
|
||||
|
||||
let mbs = doc.getElementById("mainBroadcasterSet");
|
||||
mbs.appendChild(fragBroadcasters);
|
||||
|
||||
let mps = doc.getElementById("menu_devtools_separator");
|
||||
if (mps) {
|
||||
mps.parentNode.insertBefore(fragMenuItems, mps);
|
||||
|
@ -385,10 +294,15 @@ function addTopLevelItems(doc) {
|
|||
|
||||
if (item.key && l10nKey) {
|
||||
// Create a <key>
|
||||
let key = createKey(doc, l10nKey, null, item.key);
|
||||
// Bug 371900: command event is fired only if "oncommand" attribute is set.
|
||||
key.setAttribute("oncommand", ";");
|
||||
key.addEventListener("command", item.oncommand);
|
||||
let shortcut = l10n(l10nKey + ".key");
|
||||
let key = createKey({
|
||||
doc,
|
||||
id: item.key.id,
|
||||
shortcut: shortcut,
|
||||
keytext: shortcut.startsWith("VK_") ? l10n(l10nKey + ".keytext") : null,
|
||||
modifiers: item.key.modifiers,
|
||||
oncommand: item.oncommand
|
||||
});
|
||||
// Refer to the key in order to display the key shortcut at menu ends
|
||||
menuitem.setAttribute("key", key.id);
|
||||
keys.appendChild(key);
|
||||
|
@ -396,10 +310,15 @@ function addTopLevelItems(doc) {
|
|||
if (item.additionalKeys) {
|
||||
// Create additional <key>
|
||||
for (let key of item.additionalKeys) {
|
||||
let node = createKey(doc, key.l10nKey, null, key);
|
||||
// Bug 371900: command event is fired only if "oncommand" attribute is set.
|
||||
node.setAttribute("oncommand", ";");
|
||||
node.addEventListener("command", item.oncommand);
|
||||
let shortcut = l10n(key.l10nKey + ".key");
|
||||
let node = createKey({
|
||||
doc,
|
||||
id: key.id,
|
||||
shortcut: shortcut,
|
||||
keytext: shortcut.startsWith("VK_") ? l10n(key.l10nKey + ".keytext") : null,
|
||||
modifiers: key.modifiers,
|
||||
oncommand: item.oncommand
|
||||
});
|
||||
keys.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -122,10 +122,6 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
toggleMenuItem("menu_browserToolbox", remoteEnabled);
|
||||
toggleMenuItem("menu_browserContentToolbox", remoteEnabled && win.gMultiProcessBrowser);
|
||||
|
||||
// Enable Error Console?
|
||||
let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
|
||||
toggleMenuItem("javascriptConsole", consoleEnabled);
|
||||
|
||||
// Enable DevTools connection screen, if the preference allows this.
|
||||
toggleMenuItem("menu_devtools_connect", devtoolsRemoteEnabled);
|
||||
},
|
||||
|
@ -356,6 +352,13 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
gDevToolsBrowser._trackedBrowserWindows.add(win);
|
||||
|
||||
BrowserMenus.addMenus(win.document);
|
||||
|
||||
// Inject lazily DeveloperToolbar on the chrome window
|
||||
loader.lazyGetter(win, "DeveloperToolbar", function() {
|
||||
let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar");
|
||||
return new DeveloperToolbar(win);
|
||||
});
|
||||
|
||||
this.updateCommandAvailability(win);
|
||||
this.ensurePrefObserver();
|
||||
win.addEventListener("unload", this);
|
||||
|
@ -555,6 +558,9 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
* The window containing the menu entry
|
||||
*/
|
||||
_forgetBrowserWindow: function(win) {
|
||||
if (!gDevToolsBrowser._trackedBrowserWindows.has(win)) {
|
||||
return;
|
||||
}
|
||||
gDevToolsBrowser._trackedBrowserWindows.delete(win);
|
||||
win.removeEventListener("unload", this);
|
||||
|
||||
|
@ -567,6 +573,12 @@ var gDevToolsBrowser = exports.gDevToolsBrowser = {
|
|||
}
|
||||
}
|
||||
|
||||
// Destroy the Developer toolbar if it has been accessed
|
||||
let desc = Object.getOwnPropertyDescriptor(win, "DeveloperToolbar");
|
||||
if (desc && !desc.get) {
|
||||
win.DeveloperToolbar.destroy();
|
||||
}
|
||||
|
||||
let tabContainer = win.gBrowser.tabContainer;
|
||||
tabContainer.removeEventListener("TabSelect", this, false);
|
||||
tabContainer.removeEventListener("TabOpen", this, false);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
var gItemsToTest = {
|
||||
"menu_devToolbar": "devtools.toolbar.enabled",
|
||||
"menu_browserToolbox": ["devtools.chrome.enabled", "devtools.debugger.remote-enabled"],
|
||||
"javascriptConsole": "devtools.errorconsole.enabled",
|
||||
"menu_devtools_connect": "devtools.debugger.remote-enabled",
|
||||
};
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ function testRegister(aToolbox)
|
|||
label: "Test Tool",
|
||||
inMenu: true,
|
||||
isTargetSupported: () => true,
|
||||
build: function() {}
|
||||
build: function() {},
|
||||
key: "t"
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -47,8 +48,8 @@ function toolRegistered(event, toolId)
|
|||
ok(panel, "new tool's panel exists in toolbox UI");
|
||||
|
||||
for (let win of getAllBrowserWindows()) {
|
||||
let command = win.document.getElementById("Tools:" + toolId);
|
||||
ok(command, "command for new tool added to every browser window");
|
||||
let key = win.document.getElementById("key_" + toolId);
|
||||
ok(key, "key for new tool added to every browser window");
|
||||
let menuitem = win.document.getElementById("menuitem_" + toolId);
|
||||
ok(menuitem, "menu item of new tool added to every browser window");
|
||||
}
|
||||
|
@ -89,8 +90,8 @@ function toolUnregistered(event, toolDefinition)
|
|||
ok(!panel, "tool's panel was removed from toolbox UI");
|
||||
|
||||
for (let win of getAllBrowserWindows()) {
|
||||
let command = win.document.getElementById("Tools:" + toolId);
|
||||
ok(!command, "command removed from every browser window");
|
||||
let key = win.document.getElementById("key_" + toolId);
|
||||
ok(!key , "key removed from every browser window");
|
||||
let menuitem = win.document.getElementById("menuitem_" + toolId);
|
||||
ok(!menuitem, "menu item removed from every browser window");
|
||||
}
|
||||
|
|
|
@ -11,9 +11,6 @@ devtoolsServiceWorkers.accesskey = k
|
|||
devtoolsConnect.label = Connect…
|
||||
devtoolsConnect.accesskey = C
|
||||
|
||||
errorConsoleCmd.label = Error Console
|
||||
errorConsoleCmd.accesskey = C
|
||||
|
||||
browserConsoleCmd.label = Browser Console
|
||||
browserConsoleCmd.accesskey = B
|
||||
browserConsoleCmd.key = j
|
||||
|
|
|
@ -170,14 +170,6 @@ exports.menuitems = [
|
|||
modifiers: "shift"
|
||||
}
|
||||
},
|
||||
{ id: "javascriptConsole",
|
||||
l10nKey: "errorConsoleCmd",
|
||||
disabled: true,
|
||||
oncommand(event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
window.toJavaScriptConsole();
|
||||
}
|
||||
},
|
||||
{ id: "menu_devtools_serviceworkers",
|
||||
l10nKey: "devtoolsServiceWorkers",
|
||||
disabled: true,
|
||||
|
|
|
@ -14,9 +14,6 @@ pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer
|
|||
pref("devtools.devedition.promo.enabled", false);
|
||||
#endif
|
||||
|
||||
// Disable the error console
|
||||
pref("devtools.errorconsole.enabled", false);
|
||||
|
||||
// DevTools development workflow
|
||||
pref("devtools.loader.hotreload", false);
|
||||
|
||||
|
|
|
@ -498,10 +498,6 @@ DeveloperToolbar.prototype.show = function(focus) {
|
|||
tabbrowser.addEventListener("beforeunload", this, true);
|
||||
|
||||
this._initErrorsCount(tabbrowser.selectedTab);
|
||||
this._devtoolsUnloaded = this._devtoolsUnloaded.bind(this);
|
||||
this._devtoolsLoaded = this._devtoolsLoaded.bind(this);
|
||||
Services.obs.addObserver(this._devtoolsUnloaded, "devtools-unloaded", false);
|
||||
Services.obs.addObserver(this._devtoolsLoaded, "devtools-loaded", false);
|
||||
|
||||
this._element.hidden = false;
|
||||
|
||||
|
@ -571,24 +567,6 @@ DeveloperToolbar.prototype.hide = function() {
|
|||
return this._hidePromise;
|
||||
};
|
||||
|
||||
/**
|
||||
* The devtools-unloaded event handler.
|
||||
* @private
|
||||
*/
|
||||
DeveloperToolbar.prototype._devtoolsUnloaded = function() {
|
||||
let tabbrowser = this._chromeWindow.gBrowser;
|
||||
Array.prototype.forEach.call(tabbrowser.tabs, this._stopErrorsCount, this);
|
||||
};
|
||||
|
||||
/**
|
||||
* The devtools-loaded event handler.
|
||||
* @private
|
||||
*/
|
||||
DeveloperToolbar.prototype._devtoolsLoaded = function() {
|
||||
let tabbrowser = this._chromeWindow.gBrowser;
|
||||
this._initErrorsCount(tabbrowser.selectedTab);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the listeners needed for tracking the number of errors for a given
|
||||
* tab.
|
||||
|
@ -657,8 +635,6 @@ DeveloperToolbar.prototype.destroy = function() {
|
|||
tabbrowser.removeEventListener("load", this, true);
|
||||
tabbrowser.removeEventListener("beforeunload", this, true);
|
||||
|
||||
Services.obs.removeObserver(this._devtoolsUnloaded, "devtools-unloaded");
|
||||
Services.obs.removeObserver(this._devtoolsLoaded, "devtools-loaded");
|
||||
Array.prototype.forEach.call(tabbrowser.tabs, this._stopErrorsCount, this);
|
||||
|
||||
this.focusManager.removeMonitoredElement(this.outputPanel._frame);
|
||||
|
|
|
@ -444,15 +444,17 @@ var AnimationPlayerActor = ActorClass({
|
|||
|
||||
/**
|
||||
* Get data about the animated properties of this animation player.
|
||||
* @return {Object} Returns a list of animated properties.
|
||||
* @return {Array} Returns a list of animated properties.
|
||||
* Each property contains a list of values and their offsets
|
||||
*/
|
||||
getProperties: method(function() {
|
||||
return this.player.effect.getProperties();
|
||||
return this.player.effect.getProperties().map(property => {
|
||||
return {name: property.property, values: property.values};
|
||||
});
|
||||
}, {
|
||||
request: {},
|
||||
response: {
|
||||
frames: RetVal("json")
|
||||
properties: RetVal("array:json")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
const URL = MAIN_DOMAIN + "animation.html";
|
||||
|
||||
add_task(function*() {
|
||||
let {client, walker, animations} =
|
||||
yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
|
||||
let {client, walker, animations} = yield initAnimationsFrontForUrl(URL);
|
||||
|
||||
info("Get the test node and its animation front");
|
||||
let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
|
||||
|
@ -23,7 +22,7 @@ add_task(function*() {
|
|||
is(properties.length, 1, "The correct number of properties was retrieved");
|
||||
|
||||
let propertyObject = properties[0];
|
||||
is(propertyObject.property, "transform", "Property 0 is transform");
|
||||
is(propertyObject.name, "transform", "Property 0 is transform");
|
||||
|
||||
is(propertyObject.values.length, 2,
|
||||
"The correct number of property values was retrieved");
|
||||
|
|
|
@ -267,8 +267,6 @@ DevToolsLoader.prototype = {
|
|||
}
|
||||
|
||||
if (this._provider) {
|
||||
var events = this.require("sdk/system/events");
|
||||
events.emit("devtools-unloaded", {});
|
||||
delete this.require;
|
||||
this._provider.unload("newprovider");
|
||||
}
|
||||
|
@ -330,7 +328,6 @@ DevToolsLoader.prototype = {
|
|||
reload: function() {
|
||||
var events = this.require("sdk/system/events");
|
||||
events.emit("startupcache-invalidate", {});
|
||||
events.emit("devtools-unloaded", {});
|
||||
|
||||
this._provider.unload("reload");
|
||||
delete this._provider;
|
||||
|
|
|
@ -426,8 +426,6 @@ pref("javascript.options.mem.high_water_mark", 32);
|
|||
pref("dom.max_chrome_script_run_time", 0); // disable slow script dialog for chrome
|
||||
pref("dom.max_script_run_time", 20);
|
||||
|
||||
// JS error console
|
||||
pref("devtools.errorconsole.enabled", false);
|
||||
// Absolute path to the devtools unix domain socket file used
|
||||
// to communicate with a usb cable via adb forward.
|
||||
pref("devtools.debugger.unix-domain-socket", "/data/data/@ANDROID_PACKAGE_NAME@/firefox-debugger-socket");
|
||||
|
|
|
@ -817,27 +817,22 @@ public class BrowserApp extends GeckoApp
|
|||
final String hostExtra = ContextUtils.getStringExtra(intent, INTENT_KEY_SWITCHBOARD_HOST);
|
||||
final String host = TextUtils.isEmpty(hostExtra) ? DEFAULT_SWITCHBOARD_HOST : hostExtra;
|
||||
|
||||
final String configServerUpdateUrl;
|
||||
final String configServerUrl;
|
||||
final String serverUrl;
|
||||
try {
|
||||
configServerUpdateUrl = new URL("https", host, "urls").toString();
|
||||
configServerUrl = new URL("https", host, "v1").toString();
|
||||
serverUrl = new URL("https", host, "v2").toString();
|
||||
} catch (MalformedURLException e) {
|
||||
Log.e(LOGTAG, "Error creating Switchboard server URL", e);
|
||||
return;
|
||||
}
|
||||
|
||||
SwitchBoard.initDefaultServerUrls(configServerUpdateUrl, configServerUrl, true);
|
||||
|
||||
final String switchboardUUID = ContextUtils.getStringExtra(intent, INTENT_KEY_SWITCHBOARD_UUID);
|
||||
SwitchBoard.setUUIDFromExtra(switchboardUUID);
|
||||
|
||||
// Looks at the server if there are changes in the server URL that should be used in the future
|
||||
new AsyncConfigLoader(this, AsyncConfigLoader.UPDATE_SERVER, switchboardUUID).execute();
|
||||
|
||||
// Loads the actual config. This can be done on app start or on app onResume() depending
|
||||
// how often you want to update the config.
|
||||
new AsyncConfigLoader(this, AsyncConfigLoader.CONFIG_SERVER, switchboardUUID).execute();
|
||||
// Loads the Switchboard config from the specified server URL. Eventually, we
|
||||
// should use the endpoint returned by the server URL, to support migrating
|
||||
// to a new endpoint. However, if we want to do that, we'll need to find a different
|
||||
// solution for dynamically changing the server URL from the intent.
|
||||
new AsyncConfigLoader(this, switchboardUUID, serverUrl).execute();
|
||||
}
|
||||
|
||||
private void showUpdaterPermissionSnackbar() {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
package org.mozilla.gecko.preferences;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.mozilla.gecko.AboutPages;
|
||||
import org.mozilla.gecko.AdjustConstants;
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
|
@ -17,7 +18,6 @@ import org.mozilla.gecko.EventDispatcher;
|
|||
import org.mozilla.gecko.GeckoActivityStatus;
|
||||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.GeckoApplication;
|
||||
import org.mozilla.gecko.GeckoEvent;
|
||||
import org.mozilla.gecko.GeckoProfile;
|
||||
import org.mozilla.gecko.GeckoSharedPrefs;
|
||||
import org.mozilla.gecko.LocaleManager;
|
||||
|
@ -40,7 +40,6 @@ import org.mozilla.gecko.tabqueue.TabQueuePrompt;
|
|||
import org.mozilla.gecko.updater.UpdateService;
|
||||
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.Experiments;
|
||||
import org.mozilla.gecko.util.GeckoEventListener;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.util.InputOptionsUtils;
|
||||
|
@ -90,8 +89,6 @@ import android.widget.LinearLayout;
|
|||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import com.keepsafe.switchboard.SwitchBoard;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -1154,12 +1151,28 @@ OnSharedPreferenceChangeListener
|
|||
put(AndroidImportPreference.PREF_KEY, new AndroidImportPreference.Handler());
|
||||
}};
|
||||
|
||||
private void recordSettingChangeTelemetry(String prefName, Object newValue) {
|
||||
final String value;
|
||||
if (newValue instanceof Boolean) {
|
||||
value = (Boolean) newValue ? "1" : "0";
|
||||
} else if (prefName.equals(PREFS_HOMEPAGE)) {
|
||||
// Don't record the user's homepage preference.
|
||||
value = "*";
|
||||
} else {
|
||||
value = newValue.toString();
|
||||
}
|
||||
|
||||
final JSONArray extras = new JSONArray();
|
||||
extras.put(prefName);
|
||||
extras.put(value);
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.EDIT, Method.SETTINGS, extras.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
final String prefName = preference.getKey();
|
||||
Log.i(LOGTAG, "Changed " + prefName + " = " + newValue);
|
||||
|
||||
Telemetry.sendUIEvent(TelemetryContract.Event.EDIT, Method.SETTINGS, prefName);
|
||||
recordSettingChangeTelemetry(prefName, newValue);
|
||||
|
||||
if (PREFS_MP_ENABLED.equals(prefName)) {
|
||||
showDialog((Boolean) newValue ? DIALOG_CREATE_MASTER_PASSWORD : DIALOG_REMOVE_MASTER_PASSWORD);
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package com.keepsafe.switchboard;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mozilla.gecko.background.testhelpers.TestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@RunWith(TestRunner.class)
|
||||
public class TestSwitchboard {
|
||||
|
||||
private static final String TEST_JSON = "{\"active-experiment\":{\"isActive\":true,\"values\":{\"foo\": true}},\"inactive-experiment\":{\"isActive\":false,\"values\":null}}";
|
||||
|
||||
@Before
|
||||
public void setUp() throws IOException {
|
||||
final Context c = RuntimeEnvironment.application;
|
||||
|
||||
// Avoid hitting the network by setting a config directly.
|
||||
Preferences.setDynamicConfigJson(c, TEST_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeviceUuidFactory() {
|
||||
final Context c = RuntimeEnvironment.application;
|
||||
final DeviceUuidFactory df = new DeviceUuidFactory(c);
|
||||
final UUID uuid = df.getDeviceUuid();
|
||||
assertNotNull("UUID is not null", uuid);
|
||||
assertEquals("DeviceUuidFactory always returns the same UUID", df.getDeviceUuid(), uuid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsInExperiment() {
|
||||
final Context c = RuntimeEnvironment.application;
|
||||
assertTrue("active-experiment is active", SwitchBoard.isInExperiment(c, "active-experiment"));
|
||||
assertFalse("inactive-experiment is inactive", SwitchBoard.isInExperiment(c, "inactive-experiment"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExperimentValues() throws JSONException {
|
||||
final Context c = RuntimeEnvironment.application;
|
||||
assertTrue("active-experiment has values", SwitchBoard.hasExperimentValues(c, "active-experiment"));
|
||||
assertFalse("inactive-experiment doesn't have values", SwitchBoard.hasExperimentValues(c, "inactive-experiment"));
|
||||
|
||||
final JSONObject values = SwitchBoard.getExperimentValuesFromJson(c, "active-experiment");
|
||||
assertNotNull("active-experiment values are not null", values);
|
||||
assertTrue("\"foo\" extra value is true", values.getBoolean("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetActiveExperiments() {
|
||||
final Context c = RuntimeEnvironment.application;
|
||||
final List<String> experiments = SwitchBoard.getActiveExperiments(c);
|
||||
assertNotNull("List of active experiments is not null", experiments);
|
||||
|
||||
assertTrue("List of active experiments contains active-experiemnt", experiments.contains("active-experiment"));
|
||||
assertFalse("List of active experiments does not contain inactive-experiemnt", experiments.contains("inactive-experiment"));
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,6 @@ package com.keepsafe.switchboard;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* An async loader to load user config in background thread based on internal generated UUID.
|
||||
|
@ -32,53 +31,27 @@ import android.util.Log;
|
|||
*/
|
||||
public class AsyncConfigLoader extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private String TAG = "AsyncConfigLoader";
|
||||
|
||||
public static final int UPDATE_SERVER = 1;
|
||||
public static final int CONFIG_SERVER = 2;
|
||||
|
||||
private Context context;
|
||||
private int configToLoad;
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* Sets the params for async loading either SwitchBoard.updateConfigServerUrl()
|
||||
* or SwitchBoard.loadConfig.
|
||||
* @param c Application context
|
||||
* @param configType Either UPDATE_SERVER or CONFIG_SERVER
|
||||
*/
|
||||
public AsyncConfigLoader(Context c, int configType) {
|
||||
this(c, configType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the params for async loading either SwitchBoard.updateConfigServerUrl()
|
||||
* or SwitchBoard.loadConfig.
|
||||
* Loads config with a custom UUID
|
||||
* @param c Application context
|
||||
* @param configType Either UPDATE_SERVER or CONFIG_SERVER
|
||||
* @param uuid Custom UUID
|
||||
*/
|
||||
public AsyncConfigLoader(Context c, int configType, String uuid) {
|
||||
this.context = c;
|
||||
this.configToLoad = configType;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
|
||||
if(configToLoad == UPDATE_SERVER) {
|
||||
SwitchBoard.updateConfigServerUrl(context);
|
||||
}
|
||||
else {
|
||||
if(uuid == null)
|
||||
SwitchBoard.loadConfig(context);
|
||||
else
|
||||
SwitchBoard.loadConfig(context, uuid);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
private Context context;
|
||||
private String uuid;
|
||||
private String defaultServerUrl;
|
||||
|
||||
/**
|
||||
* Sets the params for async loading either SwitchBoard.updateConfigServerUrl()
|
||||
* or SwitchBoard.loadConfig.
|
||||
* Loads config with a custom UUID
|
||||
* @param c Application context
|
||||
* @param uuid Custom UUID
|
||||
* @param defaultServerUrl Default URL endpoint for Switchboard config.
|
||||
*/
|
||||
public AsyncConfigLoader(Context c, String uuid, String defaultServerUrl) {
|
||||
this.context = c;
|
||||
this.uuid = uuid;
|
||||
this.defaultServerUrl = defaultServerUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
SwitchBoard.loadConfig(context, uuid, defaultServerUrl);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ import java.util.UUID;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.Preference;
|
||||
|
||||
|
||||
/**
|
||||
* Generates a UUID and stores is persistent as in the apps shared preferences.
|
||||
|
@ -28,53 +26,45 @@ import android.preference.Preference;
|
|||
* @author Philipp Berner
|
||||
*/
|
||||
public class DeviceUuidFactory {
|
||||
protected static final String PREFS_FILE = "com.keepsafe.switchboard.uuid";
|
||||
protected static final String PREFS_DEVICE_ID = "device_id";
|
||||
protected static final String PREFS_FILE = "com.keepsafe.switchboard.uuid";
|
||||
protected static final String PREFS_DEVICE_ID = "device_id";
|
||||
|
||||
private static UUID uuid = null;
|
||||
private static UUID uuid = null;
|
||||
|
||||
public DeviceUuidFactory(Context context) {
|
||||
public DeviceUuidFactory(Context context) {
|
||||
if (uuid == null) {
|
||||
synchronized (DeviceUuidFactory.class) {
|
||||
if (uuid == null) {
|
||||
final SharedPreferences prefs = context
|
||||
.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
|
||||
final String id = prefs.getString(PREFS_DEVICE_ID, null);
|
||||
|
||||
if (uuid == null) {
|
||||
synchronized (DeviceUuidFactory.class) {
|
||||
if (uuid == null) {
|
||||
final SharedPreferences prefs = context
|
||||
.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE);
|
||||
final String id = prefs.getString(PREFS_DEVICE_ID, null);
|
||||
if (id != null) {
|
||||
// Use the ids previously computed and stored in the prefs file
|
||||
uuid = UUID.fromString(id);
|
||||
} else {
|
||||
uuid = UUID.randomUUID();
|
||||
|
||||
if (id != null) {
|
||||
// Use the ids previously computed and stored in the
|
||||
// prefs file
|
||||
uuid = UUID.fromString(id);
|
||||
// Write the value out to the prefs file
|
||||
prefs.edit().putString(PREFS_DEVICE_ID, uuid.toString()).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Returns a unique UUID for the current android device. As with all UUIDs,
|
||||
* this unique ID is "very highly likely" to be unique across all Android
|
||||
* devices. Much more so than ANDROID_ID is.
|
||||
*
|
||||
* The UUID is generated with <code>UUID.randomUUID()</code>.
|
||||
*
|
||||
* @return a UUID that may be used to uniquely identify your device for most
|
||||
* purposes.
|
||||
*/
|
||||
public UUID getDeviceUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
UUID newId = UUID.randomUUID();
|
||||
uuid = newId;
|
||||
|
||||
// Write the value out to the prefs file
|
||||
prefs.edit()
|
||||
.putString(PREFS_DEVICE_ID, newId.toString())
|
||||
.commit();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique UUID for the current android device. As with all UUIDs,
|
||||
* this unique ID is "very highly likely" to be unique across all Android
|
||||
* devices. Much more so than ANDROID_ID is.
|
||||
*
|
||||
* The UUID is generated with <code>UUID.randomUUID()</code>.
|
||||
*
|
||||
* @return a UUID that may be used to uniquely identify your device for most
|
||||
* purposes.
|
||||
*/
|
||||
public UUID getDeviceUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
}
|
|
@ -18,7 +18,7 @@ package com.keepsafe.switchboard;
|
|||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Application preferences for SwitchBoard.
|
||||
|
@ -26,91 +26,53 @@ import android.content.SharedPreferences.Editor;
|
|||
*
|
||||
*/
|
||||
public class Preferences {
|
||||
private static final String TAG = "Preferences";
|
||||
|
||||
private static final String switchBoardSettings = "com.keepsafe.switchboard.settings";
|
||||
|
||||
//dynamic config
|
||||
private static final String kDynamicConfigServerUrl = "dynamic-config-server-url";
|
||||
private static final String kDynamicConfigServerUpdateUrl = "dynamic-config-server-update-url";
|
||||
private static final String kDynamicConfig = "dynamic-config";
|
||||
|
||||
|
||||
|
||||
//dynamic config
|
||||
/** TODO check this!!!
|
||||
* Returns a JSON string array with <br />
|
||||
* position 0 = updateserverUrl <br />
|
||||
* Fields a null if not existent.
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
public static String getDynamicUpdateServerUrl(Context c) {
|
||||
SharedPreferences settings = (SharedPreferences) Preferences.getPreferenceObject(c, false);
|
||||
return settings.getString(kDynamicConfigServerUpdateUrl, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON string array with <br />
|
||||
* postiion 1 = configServerUrl <br />
|
||||
* Fields a null if not existent.
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
public static String getDynamicConfigServerUrl(Context c) {
|
||||
SharedPreferences settings = (SharedPreferences) Preferences.getPreferenceObject(c, false);
|
||||
return settings.getString(kDynamicConfigServerUrl, null);
|
||||
}
|
||||
private static final String switchBoardSettings = "com.keepsafe.switchboard.settings";
|
||||
|
||||
/**
|
||||
* Stores the config servers URL.
|
||||
* @param c
|
||||
* @param updateServerUrl Url end point to get the current config server location
|
||||
* @param configServerUrl UR: end point to get the current endpoint for the apps config file
|
||||
* @return true if saved successful
|
||||
*/
|
||||
public static boolean setDynamicConfigServerUrl(Context c, String updateServerUrl, String configServerUrl) {
|
||||
|
||||
SharedPreferences.Editor settings = (Editor) Preferences.getPreferenceObject(c, true);
|
||||
settings.putString(kDynamicConfigServerUpdateUrl, updateServerUrl);
|
||||
settings.putString(kDynamicConfigServerUrl, configServerUrl);
|
||||
return settings.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user config as a JSON string.
|
||||
* @param c
|
||||
* @return
|
||||
*/
|
||||
public static String getDynamicConfigJson(Context c) {
|
||||
SharedPreferences settings = (SharedPreferences) Preferences.getPreferenceObject(c, false);
|
||||
return settings.getString(kDynamicConfig, null);
|
||||
}
|
||||
private static final String kDynamicConfigServerUrl = "dynamic-config-server-url";
|
||||
private static final String kDynamicConfig = "dynamic-config";
|
||||
|
||||
/**
|
||||
* Saves the user config as a JSON sting.
|
||||
* @param c
|
||||
* @param configJson
|
||||
* @return
|
||||
*/
|
||||
public static boolean setDynamicConfigJson(Context c, String configJson) {
|
||||
SharedPreferences.Editor settings = (Editor) Preferences.getPreferenceObject(c, true);
|
||||
settings.putString(kDynamicConfig, configJson);
|
||||
return settings.commit();
|
||||
}
|
||||
/**
|
||||
* Returns the stored config server URL.
|
||||
* @param c Context
|
||||
* @return URL for config endpoint.
|
||||
*/
|
||||
@Nullable public static String getDynamicConfigServerUrl(Context c) {
|
||||
final SharedPreferences prefs = c.getApplicationContext().getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE);
|
||||
return prefs.getString(kDynamicConfigServerUrl, null);
|
||||
}
|
||||
|
||||
static private Object getPreferenceObject(Context ctx, boolean writeable) {
|
||||
|
||||
Object returnValue = null;
|
||||
|
||||
Context sharedDelegate = ctx.getApplicationContext();
|
||||
|
||||
if(!writeable) {
|
||||
returnValue = sharedDelegate.getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE);
|
||||
} else {
|
||||
returnValue = sharedDelegate.getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE).edit();
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
/**
|
||||
* Stores the config servers URL.
|
||||
* @param c Context
|
||||
* @param configServerUrl URL for config endpoint.
|
||||
*/
|
||||
public static void setDynamicConfigServerUrl(Context c, String configServerUrl) {
|
||||
final SharedPreferences.Editor editor = c.getApplicationContext().
|
||||
getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE).edit();
|
||||
editor.putString(kDynamicConfigServerUrl, configServerUrl);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user config as a JSON string.
|
||||
* @param c Context
|
||||
* @return Config JSON
|
||||
*/
|
||||
@Nullable public static String getDynamicConfigJson(Context c) {
|
||||
final SharedPreferences prefs = c.getApplicationContext().getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE);
|
||||
return prefs.getString(kDynamicConfig, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the user config as a JSON sting.
|
||||
* @param c Context
|
||||
* @param configJson Config JSON
|
||||
*/
|
||||
public static void setDynamicConfigJson(Context c, String configJson) {
|
||||
final SharedPreferences.Editor editor = c.getApplicationContext().
|
||||
getSharedPreferences(switchBoardSettings, Context.MODE_PRIVATE).edit();
|
||||
editor.putString(kDynamicConfig, configJson);
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,56 +27,46 @@ import android.content.Context;
|
|||
*/
|
||||
public class Switch {
|
||||
|
||||
private Context context;
|
||||
private String experimentName;
|
||||
|
||||
/**
|
||||
* Creates an instance of a single experiment to give more convenient access to its values.
|
||||
* When the given experiment does not exist, it will give back default valued that can be found
|
||||
* in <code>Switchboard</code>. Developer has to know that experiment exists when using it.
|
||||
* @param c Application context
|
||||
* @param experimentName Name of the experiment as defined on the server
|
||||
*/
|
||||
public Switch(Context c, String experimentName) {
|
||||
this.context = c;
|
||||
this.experimentName = experimentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the experiment is active for this particular user.
|
||||
* @return Status of the experiment and false when experiment does not exist.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return SwitchBoard.isInExperiment(context, experimentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of the experiment or the given default value when experiment
|
||||
* does not exist.
|
||||
* @param defaultValue Value to return when experiment does not exist.
|
||||
* @return Experiment status
|
||||
*/
|
||||
public boolean isActive(boolean defaultValue) {
|
||||
return SwitchBoard.isInExperiment(context, experimentName, defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the experiment has aditional values.
|
||||
* @return true when values exist
|
||||
*/
|
||||
public boolean hasValues() {
|
||||
return SwitchBoard.hasExperimentValues(context, experimentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back all the experiment values in a JSONObject. This function checks if
|
||||
* values exists. If no values exist, it returns null.
|
||||
* @return Values in JSONObject or null if non
|
||||
*/
|
||||
public JSONObject getValues() {
|
||||
if(hasValues())
|
||||
return SwitchBoard.getExperimentValueFromJson(context, experimentName);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
private Context context;
|
||||
private String experimentName;
|
||||
|
||||
/**
|
||||
* Creates an instance of a single experiment to give more convenient access to its values.
|
||||
* When the given experiment does not exist, it will give back default valued that can be found
|
||||
* in <code>Switchboard</code>. Developer has to know that experiment exists when using it.
|
||||
* @param c Application context
|
||||
* @param experimentName Name of the experiment as defined on the server
|
||||
*/
|
||||
public Switch(Context c, String experimentName) {
|
||||
this.context = c;
|
||||
this.experimentName = experimentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the experiment is active for this particular user.
|
||||
* @return Status of the experiment and false when experiment does not exist.
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return SwitchBoard.isInExperiment(context, experimentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the experiment has additional values.
|
||||
* @return true when values exist
|
||||
*/
|
||||
public boolean hasValues() {
|
||||
return SwitchBoard.hasExperimentValues(context, experimentName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back all the experiment values in a JSONObject. This function checks if
|
||||
* values exists. If no values exist, it returns null.
|
||||
* @return Values in JSONObject or null if non
|
||||
*/
|
||||
public JSONObject getValues() {
|
||||
if(hasValues())
|
||||
return SwitchBoard.getExperimentValuesFromJson(context, experimentName);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
@ -33,12 +33,13 @@ import org.json.JSONException;
|
|||
import org.json.JSONObject;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
/**
|
||||
* SwitchBoard is the core class of the KeepSafe Switchboard mobile A/B testing framework.
|
||||
* This class provides a bunch of static methods that can be used in your app to run A/B tests.
|
||||
|
@ -57,397 +58,254 @@ import android.util.Log;
|
|||
*
|
||||
*/
|
||||
public class SwitchBoard {
|
||||
|
||||
private static final String TAG = "SwitchBoard";
|
||||
|
||||
/** Set if the application is run in debug mode. DynamicConfig runs against staging server when in debug and production when not */
|
||||
public static boolean DEBUG = true;
|
||||
|
||||
/** Production server to update the remote server URLs. http://staging.domain/path_to/SwitchboardURLs.php */
|
||||
private static String DYNAMIC_CONFIG_SERVER_URL_UPDATE;
|
||||
|
||||
/** Production server for getting the actual config file. http://staging.domain/path_to/SwitchboardDriver.php */
|
||||
private static String DYNAMIC_CONFIG_SERVER_DEFAULT_URL;
|
||||
|
||||
public static final String ACTION_CONFIG_FETCHED = ".SwitchBoard.CONFIG_FETCHED";
|
||||
|
||||
private static final String kUpdateServerUrl = "updateServerUrl";
|
||||
private static final String kConfigServerUrl = "configServerUrl";
|
||||
|
||||
private static final String IS_EXPERIMENT_ACTIVE = "isActive";
|
||||
private static final String EXPERIMENT_VALUES = "values";
|
||||
private static final String TAG = "SwitchBoard";
|
||||
|
||||
private static String uuidExtra = null;
|
||||
|
||||
|
||||
/**
|
||||
* Basic initialization with one server.
|
||||
* @param configServerUpdateUrl Url to: http://staging.domain/path_to/SwitchboardURLs.php
|
||||
* @param configServerUrl Url to: http://staging.domain/path_to/SwitchboardDriver.php - the acutall config
|
||||
* @param isDebug Is the application running in debug mode. This will add log messages.
|
||||
*/
|
||||
public static void initDefaultServerUrls(String configServerUpdateUrl, String configServerUrl,
|
||||
boolean isDebug) {
|
||||
|
||||
DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrl;
|
||||
DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrl;
|
||||
DEBUG = isDebug;
|
||||
}
|
||||
|
||||
public static void setUUIDFromExtra(String uuid) {
|
||||
uuidExtra = uuid;
|
||||
}
|
||||
/**
|
||||
* Advanced initialization that supports a production and staging environment without changing the server URLs manually.
|
||||
* SwitchBoard will connect to the staging environment in debug mode. This makes it very simple to test new experiements
|
||||
* during development.
|
||||
* @param configServerUpdateUrlStaging Url to http://staging.domain/path_to/SwitchboardURLs.php in staging environment
|
||||
* @param configServerUrlStaging Url to: http://staging.domain/path_to/SwitchboardDriver.php in production - the acutall config
|
||||
* @param configServerUpdateUrl Url to http://staging.domain/path_to/SwitchboardURLs.php in production environment
|
||||
* @param configServerUrl Url to: http://staging.domain/path_to/SwitchboardDriver.php in production - the acutall config
|
||||
* @param isDebug Defines if the app runs in debug.
|
||||
*/
|
||||
public static void initDefaultServerUrls(String configServerUpdateUrlStaging, String configServerUrlStaging,
|
||||
String configServerUpdateUrl, String configServerUrl,
|
||||
boolean isDebug) {
|
||||
|
||||
if(isDebug) {
|
||||
DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrlStaging;
|
||||
DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrlStaging;
|
||||
} else {
|
||||
DYNAMIC_CONFIG_SERVER_URL_UPDATE = configServerUpdateUrl;
|
||||
DYNAMIC_CONFIG_SERVER_DEFAULT_URL = configServerUrl;
|
||||
}
|
||||
|
||||
DEBUG = isDebug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the server URLs from remote and stores it locally in the app. This allows to move the server side
|
||||
* whith users already using Switchboard.
|
||||
* When there is no internet connection it will continue to use the URLs from the last time or
|
||||
* default URLS that have been set with <code>initDefaultServerUrls</code>.
|
||||
*
|
||||
* This methode should always be executed in a background thread to not block the UI.
|
||||
*
|
||||
* @param c Application context
|
||||
*/
|
||||
public static void updateConfigServerUrl(Context c) {
|
||||
if(DEBUG) Log.d(TAG, "start initConfigServerUrl");
|
||||
|
||||
if(DEBUG) {
|
||||
//set default value that is set in code for debug mode.
|
||||
Preferences.setDynamicConfigServerUrl(c, DYNAMIC_CONFIG_SERVER_URL_UPDATE, DYNAMIC_CONFIG_SERVER_DEFAULT_URL);
|
||||
return;
|
||||
}
|
||||
|
||||
//lookup new config server url from the one that is in shared prefs
|
||||
String updateServerUrl = Preferences.getDynamicUpdateServerUrl(c);
|
||||
|
||||
//set to default when not set in preferences
|
||||
if(updateServerUrl == null)
|
||||
updateServerUrl = DYNAMIC_CONFIG_SERVER_URL_UPDATE;
|
||||
|
||||
try {
|
||||
String result = readFromUrlGET(updateServerUrl, "");
|
||||
if(DEBUG) Log.d(TAG, "Result String: " + result);
|
||||
|
||||
if(result != null){
|
||||
JSONObject a = new JSONObject(result);
|
||||
|
||||
Preferences.setDynamicConfigServerUrl(c, (String)a.get(kUpdateServerUrl), (String)a.get(kConfigServerUrl));
|
||||
|
||||
if(DEBUG) Log.d(TAG, "Update Server Url: " + (String)a.get(kUpdateServerUrl));
|
||||
if(DEBUG) Log.d(TAG, "Config Server Url: " + (String)a.get(kConfigServerUrl));
|
||||
} else {
|
||||
storeDefaultUrlsInPreferences(c);
|
||||
}
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if(DEBUG) Log.d(TAG, "end initConfigServerUrl");
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a new config file for the specific user from current config server. Uses internal unique user ID.
|
||||
* Use this method only in background thread as network connections are involved that block UI thread.
|
||||
* Use AsyncConfigLoader() for easy background threading.
|
||||
* @param c ApplicationContext
|
||||
*/
|
||||
public static void loadConfig(Context c) {
|
||||
loadConfig(c, null);
|
||||
}
|
||||
/** Set if the application is run in debug mode. */
|
||||
public static boolean DEBUG = true;
|
||||
|
||||
/**
|
||||
* Loads a new config for a user. This method allows you to pass your own unique user ID instead of using
|
||||
* the SwitchBoard internal user ID.
|
||||
* Don't call method direct for background threading reasons.
|
||||
* @param c ApplicationContext
|
||||
* @param uuid Custom unique user ID
|
||||
*/
|
||||
public static void loadConfig(Context c, String uuid) {
|
||||
|
||||
try {
|
||||
|
||||
//get uuid
|
||||
if(uuid == null) {
|
||||
DeviceUuidFactory df = new DeviceUuidFactory(c);
|
||||
uuid = df.getDeviceUuid().toString();
|
||||
}
|
||||
|
||||
String device = Build.DEVICE;
|
||||
String manufacturer = Build.MANUFACTURER;
|
||||
String lang = "unknown";
|
||||
try {
|
||||
lang = Locale.getDefault().getISO3Language();
|
||||
} catch (MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String country = "unknown";
|
||||
try {
|
||||
country = Locale.getDefault().getISO3Country();
|
||||
} catch (MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String packageName = c.getPackageName();
|
||||
String versionName = "none";
|
||||
try {
|
||||
versionName = c.getPackageManager().getPackageInfo(c.getPackageName(), 0).versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
//load config, includes all experiments
|
||||
String serverUrl = Preferences.getDynamicConfigServerUrl(c);
|
||||
|
||||
if(serverUrl != null) {
|
||||
String params = "uuid="+uuid+"&device="+device+"&lang="+lang+"&country="+country
|
||||
+"&manufacturer="+manufacturer+"&appId="+packageName+"&version="+versionName;
|
||||
if(DEBUG) Log.d(TAG, "Read from server URL: " + serverUrl + "?" + params);
|
||||
String serverConfig = readFromUrlGET(serverUrl, params);
|
||||
|
||||
if(DEBUG) Log.d(TAG, serverConfig);
|
||||
|
||||
//store experiments in shared prefs (one variable)
|
||||
if(serverConfig != null)
|
||||
Preferences.setDynamicConfigJson(c, serverConfig);
|
||||
}
|
||||
|
||||
} catch (NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
private static final String IS_EXPERIMENT_ACTIVE = "isActive";
|
||||
private static final String EXPERIMENT_VALUES = "values";
|
||||
|
||||
//notify listeners that the config fetch has completed
|
||||
Intent i = new Intent(ACTION_CONFIG_FETCHED);
|
||||
LocalBroadcastManager.getInstance(c).sendBroadcast(i);
|
||||
}
|
||||
private static final String KEY_SERVER_URL = "mainServerUrl";
|
||||
private static final String KEY_CONFIG_RESULTS = "results";
|
||||
|
||||
public static boolean isInBucket(Context c, int low, int high) {
|
||||
int userBucket = getUserBucket(c);
|
||||
if (userBucket >= low && userBucket < high)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
private static String uuidExtra = null;
|
||||
|
||||
/**
|
||||
* Looks up in config if user is in certain experiment. Returns false as a default value when experiment
|
||||
* does not exist.
|
||||
* Experiment names are defined server side as Key in array for return values.
|
||||
* @param experimentName Name of the experiment to lookup
|
||||
* @return returns value for experiment or false if experiment does not exist.
|
||||
*/
|
||||
public static boolean isInExperiment(Context c, String experimentName) {
|
||||
return isInExperiment(c, experimentName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up in config if user is in certain experiment.
|
||||
* Experiment names are defined server side as Key in array for return values.
|
||||
* @param experimentName Name of the experiment to lookup
|
||||
* @param defaultReturnVal The return value that should be return when experiment does not exist
|
||||
* @return returns value for experiment or defaultReturnVal if experiment does not exist.
|
||||
*/
|
||||
public static boolean isInExperiment(Context c, String experimentName, boolean defaultReturnVal) {
|
||||
//lookup experiment in config
|
||||
String config = Preferences.getDynamicConfigJson(c);
|
||||
|
||||
//if it does not exist
|
||||
if(config == null)
|
||||
return false;
|
||||
else {
|
||||
|
||||
try {
|
||||
JSONObject experiment = (JSONObject) new JSONObject(config).get(experimentName);
|
||||
if(DEBUG) Log.d(TAG, "experiment " + experimentName + " JSON object: " + experiment.toString());
|
||||
if(experiment == null)
|
||||
return defaultReturnVal;
|
||||
|
||||
boolean returnValue = defaultReturnVal;
|
||||
returnValue = experiment.getBoolean(IS_EXPERIMENT_ACTIVE);
|
||||
|
||||
return returnValue;
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Config: " + config);
|
||||
e.printStackTrace();
|
||||
|
||||
}
|
||||
|
||||
//return false when JSON fails
|
||||
return defaultReturnVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a list of all active experiments.
|
||||
*/
|
||||
public static List<String> getActiveExperiments(Context c) {
|
||||
ArrayList<String> returnList = new ArrayList<String>();
|
||||
public static void setUUIDFromExtra(String uuid) {
|
||||
uuidExtra = uuid;
|
||||
}
|
||||
|
||||
// lookup experiment in config
|
||||
String config = Preferences.getDynamicConfigJson(c);
|
||||
/**
|
||||
* Loads a new config for a user. This method allows you to pass your own unique user ID instead of using
|
||||
* the SwitchBoard internal user ID.
|
||||
* Don't call method direct for background threading reasons.
|
||||
* @param c ApplicationContext
|
||||
* @param uuid Custom unique user ID
|
||||
* @param defaultServerUrl Default server URL endpoint.
|
||||
*/
|
||||
static void loadConfig(Context c, String uuid, @NonNull String defaultServerUrl) {
|
||||
|
||||
// if it does not exist
|
||||
if (config == null) {
|
||||
return returnList;
|
||||
}
|
||||
// Eventually, we want to check `Preferences.getDynamicConfigServerUrl(c);` before
|
||||
// falling back to the default server URL. However, this will require figuring
|
||||
// out a new solution for dynamically specifying a new server from the intent.
|
||||
String serverUrl = defaultServerUrl;
|
||||
|
||||
try {
|
||||
JSONObject experiments = new JSONObject(config);
|
||||
Iterator<?> iter = experiments.keys();
|
||||
while (iter.hasNext()) {
|
||||
String key = (String)iter.next();
|
||||
JSONObject experiment = experiments.getJSONObject(key);
|
||||
if (experiment.getBoolean(IS_EXPERIMENT_ACTIVE)) {
|
||||
returnList.add(key);
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Something went wrong!
|
||||
}
|
||||
final URL requestUrl = buildConfigRequestUrl(c, uuid, serverUrl);
|
||||
if (requestUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return returnList;
|
||||
}
|
||||
if (DEBUG) Log.d(TAG, requestUrl.toString());
|
||||
|
||||
/**
|
||||
* Checks if a certain experiment exists.
|
||||
* @param c ApplicationContext
|
||||
* @param experimentName Name of the experiment
|
||||
* @return true when experiment exists
|
||||
*/
|
||||
public static boolean hasExperimentValues(Context c, String experimentName) {
|
||||
if(getExperimentValueFromJson(c, experimentName) == null)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the experiment value as a JSONObject. Depending on what experiment is has to be converted to the right type.
|
||||
* Typcasting is by convention. You have to know what it's in there. Use <code>hasExperiment()</code>
|
||||
* before this to avoid NullPointerExceptions.
|
||||
* @param experimentName Name of the experiment to lookup
|
||||
* @return Experiment value as String, null if experiment does not exist.
|
||||
*/
|
||||
public static JSONObject getExperimentValueFromJson(Context c, String experimentName) {
|
||||
String config = Preferences.getDynamicConfigJson(c);
|
||||
|
||||
if(config == null)
|
||||
return null;
|
||||
|
||||
try {
|
||||
JSONObject experiment = (JSONObject) new JSONObject(config).get(experimentName);
|
||||
JSONObject values = experiment.getJSONObject(EXPERIMENT_VALUES);
|
||||
|
||||
return values;
|
||||
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Config: " + config);
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Could not create JSON object from config string", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets config server URLs in shared prefs to defaul when not set already. It keeps
|
||||
* URLs when already set in shared preferences.
|
||||
* @param c
|
||||
*/
|
||||
private static void storeDefaultUrlsInPreferences(Context c) {
|
||||
String configUrl = Preferences.getDynamicConfigServerUrl(c);
|
||||
String updateUrl = Preferences.getDynamicUpdateServerUrl(c);
|
||||
|
||||
if(configUrl == null)
|
||||
configUrl = DYNAMIC_CONFIG_SERVER_DEFAULT_URL;
|
||||
|
||||
if(updateUrl == null)
|
||||
updateUrl = DYNAMIC_CONFIG_SERVER_URL_UPDATE;
|
||||
|
||||
Preferences.setDynamicConfigServerUrl(c, updateUrl, configUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String containing the server response from a GET request
|
||||
* @param address Valid http addess.
|
||||
* @param params String of params. Multiple params seperated with &. No leading ? in string
|
||||
* @return Returns String from server or null when failed.
|
||||
*/
|
||||
private static String readFromUrlGET(String address, String params) {
|
||||
if(address == null || params == null)
|
||||
return null;
|
||||
|
||||
String completeUrl = address + "?" + params;
|
||||
if(DEBUG) Log.d(TAG, "readFromUrl(): " + completeUrl);
|
||||
|
||||
try {
|
||||
URL url = new URL(completeUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setUseCaches(false);
|
||||
final String result = readFromUrlGET(requestUrl);
|
||||
if (DEBUG) Log.d(TAG, result);
|
||||
|
||||
// get response
|
||||
InputStream is = connection.getInputStream();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(is);
|
||||
BufferedReader bufferReader = new BufferedReader(inputStreamReader, 8192);
|
||||
String line = "";
|
||||
StringBuffer resultContent = new StringBuffer();
|
||||
while ((line = bufferReader.readLine()) != null) {
|
||||
if(DEBUG) Log.d(TAG, line);
|
||||
resultContent.append(line);
|
||||
}
|
||||
bufferReader.close();
|
||||
|
||||
if(DEBUG) Log.d(TAG, "readFromUrl() result: " + resultContent.toString());
|
||||
|
||||
return resultContent.toString();
|
||||
} catch (ProtocolException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
final JSONObject json = new JSONObject(result);
|
||||
|
||||
/**
|
||||
* Return the bucket number of the user. There are 100 possible buckets.
|
||||
*/
|
||||
private static int getUserBucket(Context c) {
|
||||
//get uuid
|
||||
String uuid = uuidExtra;
|
||||
if (uuid == null) {
|
||||
DeviceUuidFactory df = new DeviceUuidFactory(c);
|
||||
uuid = df.getDeviceUuid().toString();
|
||||
}
|
||||
// Update the server URL if necessary.
|
||||
final String newServerUrl = json.getString(KEY_SERVER_URL);
|
||||
if (!defaultServerUrl.equals(newServerUrl)) {
|
||||
Preferences.setDynamicConfigServerUrl(c, newServerUrl);
|
||||
}
|
||||
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(uuid.getBytes());
|
||||
long checksum = crc.getValue();
|
||||
return (int)(checksum % 100L);
|
||||
}
|
||||
// Store the config in shared prefs.
|
||||
final String config = json.getString(KEY_CONFIG_RESULTS);
|
||||
Preferences.setDynamicConfigJson(c, config);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Exception parsing server result", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable private static URL buildConfigRequestUrl(Context c, String uuid, String serverUrl) {
|
||||
if (uuid == null) {
|
||||
DeviceUuidFactory df = new DeviceUuidFactory(c);
|
||||
uuid = df.getDeviceUuid().toString();
|
||||
}
|
||||
|
||||
final String device = Build.DEVICE;
|
||||
final String manufacturer = Build.MANUFACTURER;
|
||||
String lang = "unknown";
|
||||
try {
|
||||
lang = Locale.getDefault().getISO3Language();
|
||||
} catch (MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String country = "unknown";
|
||||
try {
|
||||
country = Locale.getDefault().getISO3Country();
|
||||
} catch (MissingResourceException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
final String packageName = c.getPackageName();
|
||||
String versionName = "none";
|
||||
try {
|
||||
versionName = c.getPackageManager().getPackageInfo(c.getPackageName(), 0).versionName;
|
||||
} catch (NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
final String params = "uuid="+uuid+"&device="+device+"&lang="+lang+"&country="+country
|
||||
+"&manufacturer="+manufacturer+"&appId="+packageName+"&version="+versionName;
|
||||
|
||||
try {
|
||||
return new URL(serverUrl + "?" + params);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isInBucket(Context c, int low, int high) {
|
||||
int userBucket = getUserBucket(c);
|
||||
if (userBucket >= low && userBucket < high)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up in config if user is in certain experiment. Returns false as a default value when experiment
|
||||
* does not exist.
|
||||
* Experiment names are defined server side as Key in array for return values.
|
||||
* @param experimentName Name of the experiment to lookup
|
||||
* @return returns value for experiment or false if experiment does not exist.
|
||||
*/
|
||||
public static boolean isInExperiment(Context c, String experimentName) {
|
||||
final String config = Preferences.getDynamicConfigJson(c);
|
||||
|
||||
if (config == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final JSONObject experiment = new JSONObject(config).getJSONObject(experimentName);
|
||||
if(DEBUG) Log.d(TAG, "experiment " + experimentName + " JSON object: " + experiment.toString());
|
||||
|
||||
return experiment != null && experiment.getBoolean(IS_EXPERIMENT_ACTIVE);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Error getting experiment from config", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a list of all active experiments.
|
||||
*/
|
||||
public static List<String> getActiveExperiments(Context c) {
|
||||
ArrayList<String> returnList = new ArrayList<String>();
|
||||
|
||||
// lookup experiment in config
|
||||
String config = Preferences.getDynamicConfigJson(c);
|
||||
|
||||
// if it does not exist
|
||||
if (config == null) {
|
||||
return returnList;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject experiments = new JSONObject(config);
|
||||
Iterator<?> iter = experiments.keys();
|
||||
while (iter.hasNext()) {
|
||||
String key = (String)iter.next();
|
||||
JSONObject experiment = experiments.getJSONObject(key);
|
||||
if (experiment.getBoolean(IS_EXPERIMENT_ACTIVE)) {
|
||||
returnList.add(key);
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
// Something went wrong!
|
||||
}
|
||||
|
||||
return returnList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a certain experiment has additional values.
|
||||
* @param c ApplicationContext
|
||||
* @param experimentName Name of the experiment
|
||||
* @return true when experiment exists
|
||||
*/
|
||||
public static boolean hasExperimentValues(Context c, String experimentName) {
|
||||
return getExperimentValuesFromJson(c, experimentName) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the experiment value as a JSONObject.
|
||||
* @param experimentName Name of the experiment
|
||||
* @return Experiment value as String, null if experiment does not exist.
|
||||
*/
|
||||
public static JSONObject getExperimentValuesFromJson(Context c, String experimentName) {
|
||||
final String config = Preferences.getDynamicConfigJson(c);
|
||||
|
||||
if (config == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final JSONObject experiment = new JSONObject(config).getJSONObject(experimentName);
|
||||
return experiment.getJSONObject(EXPERIMENT_VALUES);
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Could not create JSON object from config string", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a String containing the server response from a GET request
|
||||
* @param url URL for GET request.
|
||||
* @return Returns String from server or null when failed.
|
||||
*/
|
||||
@Nullable private static String readFromUrlGET(URL url) {
|
||||
if (DEBUG) Log.d(TAG, "readFromUrl(): " + url);
|
||||
|
||||
try {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setUseCaches(false);
|
||||
|
||||
InputStream is = connection.getInputStream();
|
||||
InputStreamReader inputStreamReader = new InputStreamReader(is);
|
||||
BufferedReader bufferReader = new BufferedReader(inputStreamReader, 8192);
|
||||
String line = "";
|
||||
StringBuilder resultContent = new StringBuilder();
|
||||
while ((line = bufferReader.readLine()) != null) {
|
||||
if(DEBUG) Log.d(TAG, line);
|
||||
resultContent.append(line);
|
||||
}
|
||||
bufferReader.close();
|
||||
|
||||
if(DEBUG) Log.d(TAG, "readFromUrl() result: " + resultContent.toString());
|
||||
|
||||
return resultContent.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bucket number of the user. There are 100 possible buckets.
|
||||
*/
|
||||
private static int getUserBucket(Context c) {
|
||||
//get uuid
|
||||
String uuid = uuidExtra;
|
||||
if (uuid == null) {
|
||||
DeviceUuidFactory df = new DeviceUuidFactory(c);
|
||||
uuid = df.getDeviceUuid().toString();
|
||||
}
|
||||
|
||||
CRC32 crc = new CRC32();
|
||||
crc.update(uuid.getBytes());
|
||||
long checksum = crc.getValue();
|
||||
return (int)(checksum % 100L);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ user_pref("javascript.options.showInConsole", true);
|
|||
user_pref("devtools.browsertoolbox.panel", "jsdebugger");
|
||||
user_pref("devtools.debugger.remote-port", 6023);
|
||||
user_pref("devtools.devedition.promo.enabled", false);
|
||||
user_pref("devtools.errorconsole.enabled", true);
|
||||
user_pref("browser.EULA.override", true);
|
||||
user_pref("gfx.color_management.force_srgb", true);
|
||||
user_pref("network.manage-offline-status", false);
|
||||
|
|
|
@ -2739,6 +2739,7 @@ SearchService.prototype = {
|
|||
|
||||
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_INIT_SYNC").add(true);
|
||||
this._recordEnginesWithUpdate();
|
||||
|
||||
LOG("_syncInit end");
|
||||
},
|
||||
|
@ -2781,6 +2782,7 @@ SearchService.prototype = {
|
|||
this._initObservers.resolve(this._initRV);
|
||||
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_INIT_SYNC").add(false);
|
||||
this._recordEnginesWithUpdate();
|
||||
|
||||
LOG("_asyncInit: Completed _asyncInit");
|
||||
}.bind(this));
|
||||
|
@ -3149,6 +3151,7 @@ SearchService.prototype = {
|
|||
// Typically we'll re-init as a result of a pref observer,
|
||||
// so signal to 'callers' that we're done.
|
||||
Services.obs.notifyObservers(null, SEARCH_SERVICE_TOPIC, "init-complete");
|
||||
this._recordEnginesWithUpdate();
|
||||
gInitialized = true;
|
||||
} catch (err) {
|
||||
LOG("Reinit failed: " + err);
|
||||
|
@ -4233,6 +4236,23 @@ SearchService.prototype = {
|
|||
return result;
|
||||
},
|
||||
|
||||
_recordEnginesWithUpdate: function() {
|
||||
let hasUpdates = false;
|
||||
let hasIconUpdates = false;
|
||||
for (let name in this._engines) {
|
||||
let engine = this._engines[name];
|
||||
if (engine._hasUpdates) {
|
||||
hasUpdates = true;
|
||||
if (engine._iconUpdateURL) {
|
||||
hasIconUpdates = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_HAS_UPDATES").add(hasUpdates);
|
||||
Services.telemetry.getHistogramById("SEARCH_SERVICE_HAS_ICON_UPDATES").add(hasIconUpdates);
|
||||
},
|
||||
|
||||
/**
|
||||
* This map is built lazily after the available search engines change. It
|
||||
* allows quick parsing of an URL representing a search submission into the
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
|
||||
<ShortName>update</ShortName>
|
||||
<Description>update</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Url type="text/html" method="GET" template="http://searchtest.local">
|
||||
<Param name="search" value="{searchTerms}"/>
|
||||
</Url>
|
||||
<UpdateUrl>http://searchtest.local/opensearch.xml</UpdateUrl>
|
||||
<IconUpdateUrl>http://searchtest.local/favicon.ico</IconUpdateUrl>
|
||||
</SearchPlugin>
|
|
@ -0,0 +1,36 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function run_test() {
|
||||
do_check_false(Services.search.isInitialized);
|
||||
|
||||
useHttpServer();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function checkTelemetry(histogramName, expected) {
|
||||
let histogram = Services.telemetry.getHistogramById(histogramName);
|
||||
let snapshot = histogram.snapshot();
|
||||
let expectedCounts = [0, 0, 0];
|
||||
expectedCounts[expected ? 1 : 0] = 1;
|
||||
Assert.deepEqual(snapshot.counts, expectedCounts,
|
||||
"histogram has expected content");
|
||||
histogram.clear();
|
||||
}
|
||||
|
||||
add_task(function* ignore_cache_files_without_engines() {
|
||||
yield asyncInit();
|
||||
|
||||
checkTelemetry("SEARCH_SERVICE_HAS_UPDATES", false);
|
||||
checkTelemetry("SEARCH_SERVICE_HAS_ICON_UPDATES", false);
|
||||
|
||||
// Add an engine with update urls and re-init, as we record the presence of
|
||||
// engine update urls only while initializing the search service.
|
||||
yield addTestEngines([
|
||||
{ name: "update", xmlFileName: "engine-update.xml" },
|
||||
]);
|
||||
yield asyncReInit();
|
||||
|
||||
checkTelemetry("SEARCH_SERVICE_HAS_UPDATES", true);
|
||||
checkTelemetry("SEARCH_SERVICE_HAS_ICON_UPDATES", true);
|
||||
});
|
|
@ -17,6 +17,7 @@ support-files =
|
|||
data/engine-rel-searchform-post.xml
|
||||
data/engine-rel-searchform-purpose.xml
|
||||
data/engine-system-purpose.xml
|
||||
data/engine-update.xml
|
||||
data/engineImages.xml
|
||||
data/ico-size-16x16-png.ico
|
||||
data/invalid-engine.xml
|
||||
|
@ -89,4 +90,5 @@ tags = addons
|
|||
[test_hidden.js]
|
||||
[test_currentEngine_fallback.js]
|
||||
[test_require_engines_in_cache.js]
|
||||
[test_update_telemetry.js]
|
||||
[test_svg_icon.js]
|
||||
|
|
|
@ -5375,6 +5375,22 @@
|
|||
"kind": "boolean",
|
||||
"description": "search service has been initialized synchronously"
|
||||
},
|
||||
"SEARCH_SERVICE_HAS_UPDATES": {
|
||||
"alert_emails": ["florian@mozilla.com"],
|
||||
"expires_in_version": "50",
|
||||
"kind": "boolean",
|
||||
"bug_numbers": [1259510],
|
||||
"description": "Recorded once per session near startup: records true/false whether the search service has engines with update URLs.",
|
||||
"releaseChannelCollection": "opt-out"
|
||||
},
|
||||
"SEARCH_SERVICE_HAS_ICON_UPDATES": {
|
||||
"alert_emails": ["florian@mozilla.com"],
|
||||
"expires_in_version": "50",
|
||||
"kind": "boolean",
|
||||
"bug_numbers": [1259510],
|
||||
"description": "Recorded once per session near startup: records true/false whether the search service has engines with icon update URLs.",
|
||||
"releaseChannelCollection": "opt-out"
|
||||
},
|
||||
"SEARCH_SERVICE_BUILD_CACHE_MS": {
|
||||
"expires_in_version": "40",
|
||||
"kind": "exponential",
|
||||
|
|
Загрузка…
Ссылка в новой задаче