Merge m-c to a CLOSED TREE m-i

This commit is contained in:
Phil Ringnalda 2015-01-23 22:00:41 -08:00
commit ef1df9316b
80 changed files with 2934 additions and 645 deletions

View File

@ -132,6 +132,9 @@ window.addEventListener('ContentStart', function() {
GlobalSimulatorScreen.width = width;
GlobalSimulatorScreen.height = height;
Services.prefs.setCharPref('layout.css.devPixelsPerPx',
ratio == 1 ? -1 : ratio);
// In order to do rescaling, we set the <browser> tag to the specified
// width and height, and then use a CSS transform to scale it so that
// it appears at the correct size on the host display. We also set
@ -175,8 +178,6 @@ window.addEventListener('ContentStart', function() {
style.transform +=
' rotate(0.25turn) translate(-' + shift + 'px, -' + shift + 'px)';
}
Services.prefs.setCharPref('layout.css.devPixelsPerPx', ratio);
}
// Resize on startup

View File

@ -108,7 +108,7 @@ SettingsListener.observe('language.current', 'en-US', function(value) {
Services.prefs.setCharPref(prefName, value);
if (shell.hasStarted() == false) {
shell.start();
shell.bootstrap();
}
});

View File

@ -222,6 +222,20 @@ var shell = {
return this._started;
},
bootstrap: function() {
let startManifestURL =
Cc['@mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap']
.getService(Ci.nsISupports).wrappedJSObject.startManifestURL;
if (startManifestURL) {
Cu.import('resource://gre/modules/Bootstraper.jsm');
Bootstraper.ensureSystemAppInstall(startManifestURL)
.then(this.start.bind(this))
.catch(Bootstraper.bailout);
} else {
this.start();
}
},
start: function shell_start() {
this._started = true;

View File

@ -100,6 +100,11 @@ contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds {385993f
category command-line-handler m-b2gcmds @mozilla.org/commandlinehandler/general-startup;1?type=b2gcmds
#endif
# BootstrapCommandLine.js
component {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44} BootstrapCommandLine.js
contract @mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap {fd663ec8-cf3f-4c2b-aacb-17a6915ccb44}
category command-line-handler m-b2gbootstrap @mozilla.org/commandlinehandler/general-startup;1?type=b2gbootstrap
# MobileIdentityUIGlue.js
component {83dbe26a-81f3-4a75-9541-3d0b7ca496b5} MobileIdentityUIGlue.js
contract @mozilla.org/services/mobileid-ui-glue;1 {83dbe26a-81f3-4a75-9541-3d0b7ca496b5}

View File

@ -0,0 +1,52 @@
/* 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/. */
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
function BootstrapCommandlineHandler() {
this.wrappedJSObject = this;
this.startManifestURL = null;
}
BootstrapCommandlineHandler.prototype = {
bailout: function(aMsg) {
dump("************************************************************\n");
dump("* /!\\ " + aMsg + "\n");
dump("************************************************************\n");
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
.getService(Ci.nsIAppStartup);
appStartup.quit(appStartup.eForceQuit);
},
handle: function(aCmdLine) {
this.startManifestURL = null;
try {
// Returns null if the argument was not specified. Throws
// NS_ERROR_INVALID_ARG if there is no parameter specified (because
// it was the last argument or the next argument starts with '-').
// However, someone could still explicitly pass an empty argument!
this.startManifestURL = aCmdLine.handleFlagWithParam("start-manifest", false);
} catch(e) {
return;
}
if (!this.startManifestURL) {
return;
}
if (!isAbsoluteURI(this.startManifestURL)) {
this.bailout("The start manifest url must be absolute.");
return;
}
},
helpInfo: "--start-manifest=manifest_url",
classID: Components.ID("{fd663ec8-cf3f-4c2b-aacb-17a6915ccb44}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BootstrapCommandlineHandler]);

View File

@ -0,0 +1,147 @@
/* 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";
this.EXPORTED_SYMBOLS = ["Bootstraper"];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const CC = Components.Constructor;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Webapps.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
function debug(aMsg) {
//dump("-*- Bootstraper: " + aMsg + "\n");
}
/**
* This module loads the manifest for app from the --start-url enpoint and
* ensures that it's installed as the system app.
*/
this.Bootstraper = {
_manifestURL: null,
_startupURL: null,
bailout: function(aMsg) {
dump("************************************************************\n");
dump("* /!\\ " + aMsg + "\n");
dump("************************************************************\n");
let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
.getService(Ci.nsIAppStartup);
appStartup.quit(appStartup.eForceQuit);
},
installSystemApp: function(aManifest) {
// Get the appropriate startup url from the manifest launch_path.
let base = Services.io.newURI(this._manifestURL, null, null);
let origin = base.prePath;
let helper = new ManifestHelper(aManifest, origin, this._manifestURL);
this._startupURL = helper.fullLaunchPath();
return new Promise((aResolve, aReject) => {
debug("Origin is " + origin);
let appData = {
app: {
installOrigin: origin,
origin: origin,
manifest: aManifest,
manifestURL: this._manifestURL,
manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest)),
appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED
},
appId: 1,
isBrowser: false,
isPackage: false
};
DOMApplicationRegistry.confirmInstall(appData, null, aResolve);
});
},
/**
* Resolves to a json manifest.
*/
loadManifest: function() {
return new Promise((aResolve, aReject) => {
debug("Loading manifest " + this._manifestURL);
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
xhr.mozBackgroundRequest = true;
xhr.open("GET", this._manifestURL);
xhr.responseType = "json";
xhr.addEventListener("load", () => {
if (xhr.status >= 200 && xhr.status < 400) {
debug("Success loading " + this._manifestURL);
aResolve(xhr.response);
} else {
aReject("Error loading " + this._manifestURL);
}
});
xhr.addEventListener("error", () => {
aReject("Error loading " + this._manifestURL);
});
xhr.send(null);
});
},
configure: function() {
debug("Setting startup prefs... " + this._startupURL);
Services.prefs.setCharPref("b2g.system_manifest_url", this._manifestURL);
Services.prefs.setCharPref("b2g.system_startup_url", this._startupURL);
return Promise.resolve();
},
/**
* If a system app is already installed, uninstall it so that we can
* cleanly replace it by the current one.
*/
uninstallPreviousSystemApp: function() {
let oldManifestURL;
try{
oldManifestURL = Services.prefs.getCharPref("b2g.system_manifest_url");
} catch(e) {
// No preference set, so nothing to uninstall.
return Promise.resolve();
}
let id = DOMApplicationRegistry.getAppLocalIdByManifestURL(oldManifestURL);
if (id == Ci.nsIScriptSecurityManager.NO_APP_ID) {
return Promise.resolve();
}
debug("Uninstalling " + oldManifestURL);
return DOMApplicationRegistry.uninstall(oldManifestURL);
},
/**
* Resolves once we have installed the app.
*/
ensureSystemAppInstall: function(aManifestURL) {
this._manifestURL = aManifestURL;
debug("Installing app from " + this._manifestURL);
// Check if we are already configured to run from this manifest url, and
// skip reinstall if that's the case.
try {
if (Services.prefs.getCharPref("b2g.system_manifest_url") == this._manifestURL) {
debug("Already configured for " + this._manifestURL);
return Promise.resolve();
}
} catch(e) { }
return new Promise((aResolve, aReject) => {
DOMApplicationRegistry.registryReady
.then(this.uninstallPreviousSystemApp.bind(this))
.then(this.loadManifest.bind(this))
.then(this.installSystemApp.bind(this))
.then(this.configure.bind(this))
.then(aResolve)
.catch(aReject);
});
}
};

View File

@ -12,6 +12,7 @@ EXTRA_COMPONENTS += [
'B2GAboutRedirector.js',
'B2GAppMigrator.js',
'B2GPresentationDevicePrompt.js',
'BootstrapCommandLine.js',
'ContentPermissionPrompt.js',
'FilePicker.js',
'FxAccountsUIGlue.js',
@ -49,6 +50,7 @@ if CONFIG['MOZ_UPDATER']:
EXTRA_JS_MODULES += [
'AlertsHelper.jsm',
'Bootstraper.jsm',
'ContentRequestHelper.jsm',
'DebuggerActors.js',
'ErrorPage.jsm',

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e06971db7acf7a35c32eb74d675a4e12e288e6be">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -116,7 +116,7 @@
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="f7d9bf71cf6693474f3f2a81a4ba62c0fc5646aa"/>
<project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="cfcef469537869947abb9aa1d656774cc2678d4c"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5a48c04c4bb5f079bc757e29864a42427378e051"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="f0d6ce5f727eca4b10850c610a06a45772689a75"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="d3e2de81952c45d6ed658cdf367a6e7283b9c3ce"/>
<project name="platform/system/extras" path="system/extras" revision="10e78a05252b3de785f88c2d0b9ea8a428009c50"/>
<project name="platform/system/media" path="system/media" revision="7ff72c2ea2496fa50b5e8a915e56e901c3ccd240"/>
<project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8fcd25d64f0f67d1a6f7037a4c83ce6d95466770"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fe91ec3af5396edab45b15e546e21613785724b5"/>
@ -118,7 +118,7 @@
<project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="842e33e43a55ea44833b9e23e4d180fa17c843af"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5db24726f0f42124304195a6bdea129039eeeaeb"/>
<project name="platform/system/bluetooth" path="system/bluetooth" revision="930ae098543881f47eac054677726ee4b998b2f8"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="f0d6ce5f727eca4b10850c610a06a45772689a75"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="d3e2de81952c45d6ed658cdf367a6e7283b9c3ce"/>
<project name="platform_system_core" path="system/core" remote="b2g" revision="542d1f59dc331b472307e5bd043101d14d5a3a3e"/>
<project name="platform/system/extras" path="system/extras" revision="18c1180e848e7ab8691940481f5c1c8d22c37b3e"/>
<project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8fcd25d64f0f67d1a6f7037a4c83ce6d95466770"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e06971db7acf7a35c32eb74d675a4e12e288e6be">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -116,7 +116,7 @@
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="f7d9bf71cf6693474f3f2a81a4ba62c0fc5646aa"/>
<project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="b562b01c93de9578d5db537b6a602a38e1aaa0ce"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="387f03e815f57d536dd922706db1622bddba8d81"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="f0d6ce5f727eca4b10850c610a06a45772689a75"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="d3e2de81952c45d6ed658cdf367a6e7283b9c3ce"/>
<project name="platform/system/extras" path="system/extras" revision="5356165f67f4a81c2ef28671c13697f1657590df"/>
<project name="platform/system/media" path="system/media" revision="be0e2fe59a8043fa5200f75697df9220a99abe9d"/>
<project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8fcd25d64f0f67d1a6f7037a4c83ce6d95466770"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e06971db7acf7a35c32eb74d675a4e12e288e6be">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
@ -111,7 +111,7 @@
<project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="f7d9bf71cf6693474f3f2a81a4ba62c0fc5646aa"/>
<project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="69d524e80cdf3981006627c65ac85f3a871238a3"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5a48c04c4bb5f079bc757e29864a42427378e051"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="f0d6ce5f727eca4b10850c610a06a45772689a75"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="d3e2de81952c45d6ed658cdf367a6e7283b9c3ce"/>
<project name="platform/system/extras" path="system/extras" revision="576f57b6510de59c08568b53c0fb60588be8689e"/>
<project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8fcd25d64f0f67d1a6f7037a4c83ce6d95466770"/>
<project name="platform/system/netd" path="system/netd" revision="a6531f7befb49b1c81bc0de7e51c5482b308e1c5"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fe91ec3af5396edab45b15e546e21613785724b5"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "2535321f1bd55e68fd52291b193693a8995f8e62",
"git_revision": "f74795eade46f4613741dd9e16fc43a905b40d2b",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "885e5176711ceb1401648c571a1a9290325582e7",
"revision": "a0f2cafbae71c95e9bc97d253ecacbc079e1e138",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="fe91ec3af5396edab45b15e546e21613785724b5"/>
@ -118,7 +118,7 @@
<project name="platform/prebuilts/sdk" path="prebuilts/sdk" revision="842e33e43a55ea44833b9e23e4d180fa17c843af"/>
<project name="platform/prebuilts/tools" path="prebuilts/tools" revision="5db24726f0f42124304195a6bdea129039eeeaeb"/>
<project name="platform/system/bluetooth" path="system/bluetooth" revision="930ae098543881f47eac054677726ee4b998b2f8"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="f0d6ce5f727eca4b10850c610a06a45772689a75"/>
<project name="platform_system_bluetoothd" path="system/bluetoothd" remote="b2g" revision="d3e2de81952c45d6ed658cdf367a6e7283b9c3ce"/>
<project name="platform_system_core" path="system/core" remote="b2g" revision="542d1f59dc331b472307e5bd043101d14d5a3a3e"/>
<project name="platform/system/extras" path="system/extras" revision="18c1180e848e7ab8691940481f5c1c8d22c37b3e"/>
<project name="platform_system_libfdio" path="system/libfdio" remote="b2g" revision="8fcd25d64f0f67d1a6f7037a4c83ce6d95466770"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="2535321f1bd55e68fd52291b193693a8995f8e62"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="f74795eade46f4613741dd9e16fc43a905b40d2b"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2262d4a77d4f46ab230fd747bb91e9b77bad36cb"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -477,6 +477,7 @@
@BINPATH@/components/OopCommandLine.js
@BINPATH@/components/CommandLine.js
#endif
@BINPATH@/components/BootstrapCommandLine.js
#ifdef MOZ_UPDATER
@BINPATH@/components/nsUpdateService.manifest

View File

@ -1173,7 +1173,9 @@ toolbarpaletteitem[dragover] {
toolbarpaletteitem[place="palette"] {
width: 10em;
height: calc(40px + 2em);
/* icon (32) + margin (2 * 4) + button padding/border (2 * 4) + label margin (~2) + label
* line-height (1.5em): */
height: calc(50px + 1.5em);
margin-bottom: 5px;
overflow: hidden;
display: inline-block;

View File

@ -208,16 +208,20 @@ PlayerWidget.prototype = {
titleHTML += L10N.getStr("player.animationDurationLabel");
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(state.duration)) + "</strong>";
if (state.delay) {
titleHTML += L10N.getStr("player.animationDelayLabel");
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(state.delay)) + "</strong>";
}
titleHTML += L10N.getStr("player.animationIterationCountLabel");
let count = state.iterationCount || L10N.getStr("player.infiniteIterationCount");
titleHTML += "<strong>" + count + "</strong>";
titleHTML += "</span>"
if (state.iterationCount !== 1) {
titleHTML += L10N.getStr("player.animationIterationCountLabel");
let count = state.iterationCount || L10N.getStr("player.infiniteIterationCount");
titleHTML += "<strong>" + count + "</strong>";
}
titleHTML += "</span>";
titleEl.innerHTML = titleHTML;
// Timeline widget.
@ -254,9 +258,9 @@ PlayerWidget.prototype = {
}
});
let max = state.duration; // Infinite iterations.
let max = state.duration;
if (state.iterationCount) {
// Finite iterations.
// If there's a finite nb of iterations.
max = state.iterationCount * state.duration;
}
@ -285,10 +289,11 @@ PlayerWidget.prototype = {
"class": "time-display"
}
});
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(0));
this.containerEl.appendChild(this.el);
// Show the initial time.
this.displayTime(state.currentTime);
},
/**
@ -323,11 +328,13 @@ PlayerWidget.prototype = {
*/
onStateChanged: function() {
let state = this.player.state;
this.updatePlayPauseButton(state.playState);
this.updateWidgetState(state.playState);
switch (state.playState) {
case "finished":
this.destroy();
this.stopTimelineAnimation();
this.displayTime(this.player.state.duration);
this.stopListeners();
break;
case "running":
this.startTimelineAnimation();
@ -347,7 +354,7 @@ PlayerWidget.prototype = {
pause: function() {
// Switch to the right className on the element right away to avoid waiting
// for the next state update to change the playPause icon.
this.updatePlayPauseButton("paused");
this.updateWidgetState("paused");
return this.player.pause().then(() => {
this.stopTimelineAnimation();
});
@ -361,12 +368,12 @@ PlayerWidget.prototype = {
play: function() {
// Switch to the right className on the element right away to avoid waiting
// for the next state update to change the playPause icon.
this.updatePlayPauseButton("running");
this.updateWidgetState("running");
this.startTimelineAnimation();
return this.player.play();
},
updatePlayPauseButton: function(playState) {
updateWidgetState: function(playState) {
this.el.className = "player-widget " + playState;
},
@ -377,10 +384,12 @@ PlayerWidget.prototype = {
startTimelineAnimation: function() {
this.stopTimelineAnimation();
let state = this.player.state;
let start = performance.now();
let loop = () => {
this.rafID = requestAnimationFrame(loop);
let now = this.player.state.currentTime + performance.now() - start;
let now = state.currentTime + performance.now() - start;
this.displayTime(now);
};
@ -399,13 +408,22 @@ PlayerWidget.prototype = {
time = Math.max(0, time - state.delay);
}
// For finite animations, make sure the displayed time does not go beyond
// the animation total duration (this may happen due to the local
// requestAnimationFrame loop).
if (state.iterationCount) {
time = Math.min(time, state.iterationCount * state.duration);
}
// Set the time label value.
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
this.getFormattedTime(time));
// Set the timeline slider value.
if (!state.iterationCount && time !== state.duration) {
this.currentTimeEl.value = time % state.duration;
} else {
this.currentTimeEl.value = time;
time = time % state.duration;
}
this.currentTimeEl.value = time;
},
/**

View File

@ -6,15 +6,19 @@ support-files =
head.js
[browser_animation_empty_on_invalid_nodes.js]
[browser_animation_iterationCount_hidden_by_default.js]
[browser_animation_panel_exists.js]
[browser_animation_participate_in_inspector_update.js]
[browser_animation_play_pause_button.js]
[browser_animation_playerFronts_are_refreshed.js]
[browser_animation_playerWidgets_destroy.js]
[browser_animation_playerWidgets_disables_on_finished.js]
[browser_animation_playerWidgets_dont_show_time_after_duration.js]
[browser_animation_playerWidgets_meta_data.js]
[browser_animation_playerWidgets_state_after_pause.js]
[browser_animation_refresh_when_active.js]
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
[browser_animation_shows_player_on_valid_node.js]
[browser_animation_timeline_animates.js]
[browser_animation_timeline_waits_for_delay.js]
[browser_animation_ui_updates_when_animation_changes.js]
[browser_animation_ui_updates_when_animation_changes.js]

View File

@ -0,0 +1,24 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that iteration count is only shown in the UI when it's different than 1
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting a node with an animation that doesn't repeat");
yield selectNode(".long", inspector);
let widget = panel.playerWidgets[0];
let metaDataLabels = widget.el.querySelectorAll(".animation-title .meta-data strong");
is(metaDataLabels.length, 1, "Only the duration is shown");
info("Selecting a node with an animation that repeats several times");
yield selectNode(".delayed", inspector);
widget = panel.playerWidgets[0];
let iterationLabel = widget.el.querySelectorAll(".animation-title .meta-data strong")[2];
is(iterationLabel.textContent, "10", "The iteration is shown");
});

View File

@ -0,0 +1,40 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that when animations end, the corresponding player widgets are disabled.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel, controller} = yield openAnimationInspector();
info("Apply 2 finite animations to the test node");
getNode(".still").classList.add("multi-finite");
info("Select the test node");
yield selectNode(".still", inspector);
is(controller.animationPlayers.length, 2, "2 animation players exist");
info("Wait for both animations to end");
let promises = controller.animationPlayers.map(front => {
let def = promise.defer();
let onStateChanged = () => {
if (front.state.playState === "finished") {
front.off(front.AUTO_REFRESH_EVENT, onStateChanged);
def.resolve();
}
};
front.on(front.AUTO_REFRESH_EVENT, onStateChanged);
return def.promise;
});
yield promise.all(promises);
for (let widgetEl of panel.playersEl.querySelectorAll(".player-widget")) {
ok(widgetEl.classList.contains("finished"), "The player widget has the right class");
}
});

View File

@ -0,0 +1,40 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that after the animation has ended, the current time label and timeline
// slider don't show values bigger than the animation duration (which would
// happen if the local requestAnimationFrame loop didn't stop correctly).
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Start an animation on the test node");
getNode(".still").classList.add("short");
info("Select the node");
yield selectNode(".still", inspector);
info("Wait until the animation ends");
let widget = panel.playerWidgets[0];
let front = widget.player;
let def = promise.defer();
let onStateChanged = () => {
if (front.state.playState === "finished") {
front.off(front.AUTO_REFRESH_EVENT, onStateChanged);
def.resolve();
}
};
front.on(front.AUTO_REFRESH_EVENT, onStateChanged);
yield def.promise;
is(widget.currentTimeEl.value, front.state.duration,
"The timeline slider has the right value");
is(widget.timeDisplayEl.textContent,
widget.getFormattedTime(front.state.duration) + "s",
"The timeline slider has the right value");
});

View File

@ -0,0 +1,30 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test that once an animation is paused and its widget is refreshed, the right
// initial time is displayed.
add_task(function*() {
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
let {inspector, panel} = yield openAnimationInspector();
info("Selecting the test node");
yield selectNode(".animated", inspector);
info("Pausing the animation by using the widget");
let widget = panel.playerWidgets[0];
yield widget.pause();
info("Selecting another node and then the same node again to refresh the widget");
yield selectNode(".still", inspector);
yield selectNode(".animated", inspector);
widget = panel.playerWidgets[0];
ok(widget.el.classList.contains("paused"), "The widget is still in paused mode");
is(widget.timeDisplayEl.textContent,
widget.getFormattedTime(widget.player.state.currentTime) + "s",
"The initial time has been set to the player's");
});

View File

@ -40,6 +40,31 @@
animation: simple-animation 3s 60s 10;
}
.multi-finite {
top: 400px;
left: 10px;
background: yellow;
animation: simple-animation 3s,
other-animation 4s;
}
.short {
top: 500px;
left: 10px;
background: red;
animation: simple-animation 2s
}
.long {
top: 600px;
left: 10px;
background: blue;
animation: simple-animation 120s
}
@keyframes simple-animation {
100% {
transform: translateX(300px);
@ -59,5 +84,8 @@
<div class="ball animated"></div>
<div class="ball multi"></div>
<div class="ball delayed"></div>
<div class="ball multi-finite"></div>
<div class="ball short"></div>
<div class="ball long"></div>
</body>
</html>

View File

@ -26,6 +26,9 @@ const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
const WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
const l10n = new WebConsoleUtils.l10n(STRINGS_URI);
const MAX_STRING_GRIP_LENGTH = 36;
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
// Constants for compatibility with the Web Console output implementation before
// bug 778766.
// TODO: remove these once bug 778766 is fixed.
@ -1126,6 +1129,29 @@ Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
return result;
},
/**
* Shorten grips of the type string, leaves other grips unmodified.
*
* @param object grip
* Value grip from the server.
* @return object
* Possible values of object:
* - A shortened string, if original grip was of string type.
* - The unmodified input grip, if it wasn't of string type.
*/
shortenValueGrip: function(grip)
{
let shortVal = grip;
if (typeof(grip)=="string") {
shortVal = grip.replace(/(\r\n|\n|\r)/gm," ");
if (shortVal.length > MAX_STRING_GRIP_LENGTH) {
shortVal = shortVal.substring(0,MAX_STRING_GRIP_LENGTH - 1) + ELLIPSIS;
}
}
return shortVal;
},
/**
* Get a CodeMirror-compatible class name for a given value grip.
*
@ -2356,7 +2382,8 @@ Widgets.JSObject.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
if (valueIsText) {
this._text(value);
} else {
let valueElem = this.message._renderValueGrip(value, { concise: true });
let shortVal = this.message.shortenValueGrip(value);
let valueElem = this.message._renderValueGrip(shortVal, { concise: true });
container.appendChild(valueElem);
}
},
@ -2676,7 +2703,9 @@ Widgets.ObjectRenderers.add({
this._renderEmptySlots(emptySlots);
emptySlots = 0;
}
let elem = this.message._renderValueGrip(item, { concise: true });
let shortVal = this.message.shortenValueGrip(item);
let elem = this.message._renderValueGrip(shortVal, { concise: true });
this.element.appendChild(elem);
}
}

View File

@ -6,6 +6,7 @@
// Test the webconsole output for various types of objects.
const TEST_URI = "data:text/html;charset=utf8,test for console output - 05";
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
let dateNow = Date.now();
@ -103,6 +104,14 @@ let inputTests = [
printOutput: "[object Promise]",
inspectable: true,
variablesViewLabel: "Promise"
},
//11
{
input: "new Object({1: 'this\\nis\\nsupposed\\nto\\nbe\\na\\nvery\\nlong\\nstring\\n,shown\\non\\na\\nsingle\\nline', 2: 'a shorter string', 3: 100})",
output: 'Object { 1: "this is supposed to be a very long ' + ELLIPSIS + '", 2: "a shorter string", 3: 100 }',
printOutput: "[object Object]",
inspectable: false,
}
];

View File

@ -6,6 +6,9 @@
// Test the webconsole output for various arrays.
const TEST_URI = "data:text/html;charset=utf8,test for console output - 06";
const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
const test_str_in = "SHOW\\nALL\\nOF\\nTHIS\\nON\\nA\\nSINGLE\\nLINE ONLY. ESCAPE ALL NEWLINE";
const test_str_out = "SHOW ALL OF THIS ON A SINGLE LINE O" + ELLIPSIS;
let inputTests = [
// 1 - array with empty slots only
@ -97,6 +100,15 @@ let inputTests = [
printOutput: "0,,,3,4,5",
inspectable: true,
variablesViewLabel: "Array[6]"
},
//12 - array with long strings as elements
{
input: '["' + test_str_in + '", "' + test_str_in + '", "' + test_str_in + '"]',
output: 'Array [ "' + test_str_out + '", "' + test_str_out + '", "' + test_str_out + '" ]',
inspectable: false,
printOutput: "SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE ALL NEWLINE,SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE ALL NEWLINE,SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE ALL NEWLINE",
variablesViewLabel: "Array[3]"
}
];

View File

@ -3,25 +3,24 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cu = Components.utils;
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm");
const {AppManager} = require("devtools/webide/app-manager");
const {Connection} = require("devtools/client/connection-manager");
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
const ConfigView = require("devtools/webide/config-view");
let devicePrefsKeys = {};
let table;
let configView = new ConfigView(window);
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
document.getElementById("close").onclick = CloseUI;
AppManager.on("app-manager-update", OnAppManagerUpdate);
document.getElementById("device-preferences").onchange = UpdatePref;
document.getElementById("device-preferences").onclick = CheckReset;
document.getElementById("search-bar").onkeyup = document.getElementById("search-bar").onclick = SearchPref;
document.getElementById("custom-value").onclick = UpdateNewPref;
document.getElementById("custom-value-type").onchange = ClearNewPrefs;
document.getElementById("add-custom-preference").onkeyup = CheckNewPrefSubmit;
document.getElementById("close").onclick = CloseUI;
document.getElementById("device-fields").onchange = UpdateField;
document.getElementById("device-fields").onclick = CheckReset;
document.getElementById("search-bar").onkeyup = document.getElementById("search-bar").onclick = SearchField;
document.getElementById("custom-value").onclick = UpdateNewField;
document.getElementById("custom-value-type").onchange = ClearNewFields;
document.getElementById("add-custom-field").onkeyup = CheckNewFieldSubmit;
BuildUI();
}, true);
@ -40,280 +39,49 @@ function OnAppManagerUpdate(event, what) {
}
}
function RenderByType(input, name, value, customType) {
value = customType || typeof value;
switch (value) {
case "boolean":
input.setAttribute("data-type", "boolean");
input.setAttribute("type", "checkbox");
break;
case "number":
input.setAttribute("data-type", "number");
input.setAttribute("type", "number");
break;
default:
input.setAttribute("data-type", "string");
input.setAttribute("type", "text");
break;
}
return input;
function CheckNewFieldSubmit(event) {
configView.checkNewFieldSubmit(event);
}
let defaultPref; // Used by tests
function ResetToDefault(name, input, button) {
AppManager.preferenceFront.clearUserPref(name);
let dataType = input.getAttribute("data-type");
let tr = document.getElementById("row-" + name);
switch (dataType) {
case "boolean":
defaultPref = AppManager.preferenceFront.getBoolPref(name);
defaultPref.then(boolean => {
input.checked = boolean;
}, () => {
input.checked = false;
tr.parentNode.removeChild(tr);
});
break;
case "number":
defaultPref = AppManager.preferenceFront.getIntPref(name);
defaultPref.then(number => {
input.value = number;
}, () => {
tr.parentNode.removeChild(tr);
});
break;
default:
defaultPref = AppManager.preferenceFront.getCharPref(name);
defaultPref.then(string => {
input.value = string;
}, () => {
tr.parentNode.removeChild(tr);
});
break;
}
button.classList.add("hide");
function UpdateNewField() {
configView.updateNewField();
}
function SaveByType(options) {
let prefName = options.id;
let inputType = options.type;
let value = options.value;
let input = document.getElementById(prefName);
switch (inputType) {
case "boolean":
AppManager.preferenceFront.setBoolPref(prefName, input.checked);
break;
case "number":
AppManager.preferenceFront.setIntPref(prefName, value);
break;
default:
AppManager.preferenceFront.setCharPref(prefName, value);
break;
}
}
function CheckNewPrefSubmit(event) {
if (event.keyCode === 13) {
document.getElementById("custom-value").click();
}
}
function ClearNewPrefs() {
let customTextEl = table.querySelector("#custom-value-text");
if (customTextEl.checked) {
customTextEl.checked = false;
} else {
customTextEl.value = "";
}
UpdateFieldType();
}
function UpdateFieldType() {
let customValueType = table.querySelector("#custom-value-type").value;
let customTextEl = table.querySelector("#custom-value-text");
let customText = customTextEl.value;
if (customValueType.length === 0) {
return false;
}
switch (customValueType) {
case "boolean":
customTextEl.type = "checkbox";
customText = customTextEl.checked;
break;
case "number":
customText = parseInt(customText, 10) || 0;
customTextEl.type = "number";
break;
default:
customTextEl.type = "text";
break;
}
return customValueType;
}
function UpdateNewPref(event) {
let customValueType = UpdateFieldType();
if (!customValueType) {
return;
}
let customRow = table.querySelector("tr:nth-of-type(2)");
let customTextEl = table.querySelector("#custom-value-text");
let customTextNameEl = table.querySelector("#custom-value-name");
if (customTextEl.validity.valid) {
let customText = customTextEl.value;
if (customValueType === "boolean") {
customText = customTextEl.checked;
}
let customTextName = customTextNameEl.value.replace(/[^A-Za-z0-9\.\-_]/gi, "");
GenerateField(customTextName, customText, true, customValueType, customRow);
SaveByType({
id: customTextName,
type: customValueType,
value: customText
});
customTextNameEl.value = "";
ClearNewPrefs();
}
function ClearNewFields() {
configView.clearNewFields();
}
function CheckReset(event) {
if (event.target.classList.contains("reset")) {
let btnId = event.target.getAttribute("data-id");
let input = document.getElementById(btnId);
ResetToDefault(btnId, input, event.target);
}
configView.checkReset(event);
}
function UpdatePref(event) {
if (event.target) {
let inputType = event.target.getAttribute("data-type");
let inputValue = event.target.checked || event.target.value;
if (event.target.nodeName == "input" &&
event.target.validity.valid &&
event.target.classList.contains("editable")) {
let id = event.target.id;
if (inputType == "boolean") {
if (event.target.checked) {
inputValue = true;
} else {
inputValue = false;
}
}
SaveByType({
id: id,
type: inputType,
value: inputValue
});
document.getElementById("btn-" + id).classList.remove("hide");
}
}
function UpdateField(event) {
configView.updateField(event);
}
function GenerateField(name, value, hasUserValue, customType, newPreferenceRow) {
if (name.length < 1) {
return;
}
let sResetDefault = Strings.GetStringFromName("devicepreferences_reset_default");
devicePrefsKeys[name] = true;
let input = document.createElement("input");
let tr = document.createElement("tr");
tr.setAttribute("id", "row-" + name);
tr.classList.add("edit-row");
let td = document.createElement("td");
td.classList.add("preference-name");
td.textContent = name;
tr.appendChild(td);
td = document.createElement("td");
input.classList.add("editable");
input.setAttribute("id", name);
input = RenderByType(input, name, value, customType);
if (customType === "boolean" || input.type === "checkbox") {
input.checked = value;
} else {
input.value = value;
}
td.appendChild(input);
tr.appendChild(td);
td = document.createElement("td");
td.setAttribute("id", "td-" + name);
let button = document.createElement("button");
button.setAttribute("data-id", name);
button.setAttribute("id", "btn-" + name);
button.classList.add("reset");
button.textContent = sResetDefault;
td.appendChild(button);
if (!hasUserValue) {
button.classList.add("hide");
}
tr.appendChild(td);
// If this is a new preference, add it to the top of the table.
if (newPreferenceRow) {
let existingPref = table.querySelector("#" + name);
if (!existingPref) {
table.insertBefore(tr, newPreferenceRow);
} else {
existingPref.value = value;
}
} else {
table.appendChild(tr);
}
}
function SearchPref(event) {
if (event.target.value.length) {
let stringMatch = new RegExp(event.target.value, "i");
for (let key in devicePrefsKeys) {
let row = document.getElementById("row-" + key);
if (key.match(stringMatch)) {
row.classList.remove("hide");
} else if (row) {
row.classList.add("hide");
}
}
} else {
var trs = document.getElementsByTagName("tr");
for (let i = 0; i < trs.length; i++) {
trs[i].classList.remove("hide");
}
}
function SearchField(event) {
configView.search(event);
}
let getAllPrefs; // Used by tests
function BuildUI() {
table = document.querySelector("table");
let trs = table.querySelectorAll("tr:not(#add-custom-preference)");
for (var i = 0; i < trs.length; i++) {
table.removeChild(trs[i]);
}
configView.resetTable();
if (AppManager.connection &&
AppManager.connection.status == Connection.Status.CONNECTED &&
AppManager.preferenceFront) {
configView.front = AppManager.preferenceFront;
configView.kind = "Pref";
configView.includeTypeName = true;
getAllPrefs = AppManager.preferenceFront.getAllPrefs();
getAllPrefs.then(json => {
let devicePrefs = Object.keys(json);
devicePrefs.sort();
for (let i = 0; i < devicePrefs.length; i++) {
GenerateField(devicePrefs[i], json[devicePrefs[i]].value, json[devicePrefs[i]].hasUserValue);
let deviceItems = Object.keys(json);
deviceItems.sort();
configView.keys = deviceItems;
for (let i = 0; i < configView.keys.length; i++) {
let key = configView.keys[i];
configView.generateField(key, json[key].value, json[key].hasUserValue);
}
});
} else {

View File

@ -13,7 +13,7 @@
<head>
<meta charset="utf8"/>
<link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
<link rel="stylesheet" href="chrome://webide/skin/devicepreferences.css" type="text/css"/>
<link rel="stylesheet" href="chrome://webide/skin/config-view.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://webide/content/devicepreferences.js"></script>
</head>
<body>
@ -21,27 +21,27 @@
<div id="controls">
<a id="close">&deck_close;</a>
</div>
<h1>&devicepreferences_title;</h1>
<h1>&devicepreference_title;</h1>
<div id="search">
<input type="text" id="search-bar" placeholder="&devicepreferences_search;"/>
<input type="text" id="search-bar" placeholder="&devicepreference_search;"/>
</div>
</header>
<table id="device-preferences">
<tr id="add-custom-preference">
<table id="device-fields">
<tr id="add-custom-field">
<td>
<select id="custom-value-type">
<option value="" selected="selected">&devicepreferences_typenone;</option>
<option value="boolean">&devicepreferences_typeboolean;</option>
<option value="number">&devicepreferences_typenumber;</option>
<option value="string">&devicepreferences_typestring;</option>
<option value="" selected="selected">&device_typenone;</option>
<option value="boolean">&device_typeboolean;</option>
<option value="number">&device_typenumber;</option>
<option value="string">&device_typestring;</option>
</select>
<input type="text" id="custom-value-name" placeholder="&devicepreferences_newname;"/>
<input type="text" id="custom-value-name" placeholder="&devicepreference_newname;"/>
</td>
<td class="custom-input">
<input type="text" id="custom-value-text" placeholder="&devicepreferences_newtext;"/>
<input type="text" id="custom-value-text" placeholder="&devicepreference_newtext;"/>
</td>
<td>
<button id="custom-value" class="new-editable">&devicepreferences_addnew;</button>
<button id="custom-value" class="new-editable">&devicepreference_addnew;</button>
</td>
</tr>
</table>

View File

@ -0,0 +1,90 @@
/* 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/. */
const Cu = Components.utils;
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm");
const {AppManager} = require("devtools/webide/app-manager");
const {Connection} = require("devtools/client/connection-manager");
const ConfigView = require("devtools/webide/config-view");
let configView = new ConfigView(window);
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad);
AppManager.on("app-manager-update", OnAppManagerUpdate);
document.getElementById("close").onclick = CloseUI;
document.getElementById("device-fields").onchange = UpdateField;
document.getElementById("device-fields").onclick = CheckReset;
document.getElementById("search-bar").onkeyup = document.getElementById("search-bar").onclick = SearchField;
document.getElementById("custom-value").onclick = UpdateNewField;
document.getElementById("custom-value-type").onchange = ClearNewFields;
document.getElementById("add-custom-field").onkeyup = CheckNewFieldSubmit;
BuildUI();
}, true);
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload);
AppManager.off("app-manager-update", OnAppManagerUpdate);
});
function CloseUI() {
window.parent.UI.openProject();
}
function OnAppManagerUpdate(event, what) {
if (what == "connection" || what == "list-tabs-response") {
BuildUI();
}
}
function CheckNewFieldSubmit(event) {
configView.checkNewFieldSubmit(event);
}
function UpdateNewField() {
configView.updateNewField();
}
function ClearNewFields() {
configView.clearNewFields();
}
function CheckReset(event) {
configView.checkReset(event);
}
function UpdateField(event) {
configView.updateField(event);
}
function SearchField(event) {
configView.search(event);
}
let getAllSettings; // Used by tests
function BuildUI() {
configView.resetTable();
if (AppManager.connection &&
AppManager.connection.status == Connection.Status.CONNECTED &&
AppManager.settingsFront) {
configView.front = AppManager.settingsFront;
configView.kind = "Setting";
configView.includeTypeName = false;
getAllSettings = AppManager.settingsFront.getAllSettings()
getAllSettings.then(json => {
let deviceItems = Object.keys(json);
deviceItems.sort();
configView.keys = deviceItems;
for (let i = 0; i < configView.keys.length; i++) {
let key = configView.keys[i];
configView.generateField(key, json[key].value, json[key].hasUserValue);
}
});
} else {
CloseUI();
}
}

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % webideDTD SYSTEM "chrome://browser/locale/devtools/webide.dtd" >
%webideDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf8"/>
<link rel="stylesheet" href="chrome://webide/skin/deck.css" type="text/css"/>
<link rel="stylesheet" href="chrome://webide/skin/config-view.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://webide/content/devicesettings.js"></script>
</head>
<body>
<header>
<div id="controls">
<a id="close">&deck_close;</a>
</div>
<h1>&devicesetting_title;</h1>
<div id="search">
<input type="text" id="search-bar" placeholder="&devicesetting_search;"/>
</div>
</header>
<table id="device-fields">
<tr id="add-custom-field">
<td>
<select id="custom-value-type">
<option value="" selected="selected">&device_typenone;</option>
<option value="boolean">&device_typeboolean;</option>
<option value="number">&device_typenumber;</option>
<option value="string">&device_typestring;</option>
<option value="object">&device_typeobject;</option>
</select>
<input type="text" id="custom-value-name" placeholder="&devicesetting_newname;"/>
</td>
<td class="custom-input">
<input type="text" id="custom-value-text" placeholder="&devicesetting_newtext;"/>
</td>
<td>
<button id="custom-value" class="new-editable">&devicesetting_addnew;</button>
</td>
</tr>
</table>
</body>
</html>

View File

@ -22,3 +22,5 @@ webide.jar:
content/monitor.js (monitor.js)
content/devicepreferences.js (devicepreferences.js)
content/devicepreferences.xhtml (devicepreferences.xhtml)
content/devicesettings.js (devicesettings.js)
content/devicesettings.xhtml (devicesettings.xhtml)

View File

@ -806,6 +806,7 @@ let UI = {
document.querySelector("#cmd_stop").setAttribute("disabled", "true");
document.querySelector("#cmd_toggleToolbox").setAttribute("disabled", "true");
document.querySelector("#cmd_showDevicePrefs").setAttribute("disabled", "true");
document.querySelector("#cmd_showSettings").setAttribute("disabled", "true");
return;
}
@ -870,6 +871,7 @@ let UI = {
let detailsCmd = document.querySelector("#cmd_showRuntimeDetails");
let disconnectCmd = document.querySelector("#cmd_disconnectRuntime");
let devicePrefsCmd = document.querySelector("#cmd_showDevicePrefs");
let settingsCmd = document.querySelector("#cmd_showSettings");
let box = document.querySelector("#runtime-actions");
@ -883,6 +885,9 @@ let UI = {
if (AppManager.preferenceFront) {
devicePrefsCmd.removeAttribute("disabled");
}
if (AppManager.settingsFront) {
settingsCmd.removeAttribute("disabled");
}
disconnectCmd.removeAttribute("disabled");
runtimePanelButton.setAttribute("active", "true");
} else {
@ -891,6 +896,7 @@ let UI = {
screenshotCmd.setAttribute("disabled", "true");
disconnectCmd.setAttribute("disabled", "true");
devicePrefsCmd.setAttribute("disabled", "true");
settingsCmd.setAttribute("disabled", "true");
runtimePanelButton.removeAttribute("active");
}
@ -1248,6 +1254,10 @@ let Cmds = {
UI.selectDeckPanel("devicepreferences");
},
showSettings: function() {
UI.selectDeckPanel("devicesettings");
},
showMonitor: function() {
UI.selectDeckPanel("monitor");
},

View File

@ -36,6 +36,7 @@
<command id="cmd_importPackagedApp" oncommand="Cmds.importPackagedApp()" label="&projectMenu_importPackagedApp_label;"/>
<command id="cmd_importHostedApp" oncommand="Cmds.importHostedApp()" label="&projectMenu_importHostedApp_label;"/>
<command id="cmd_showDevicePrefs" label="&runtimeMenu_showDevicePrefs_label;" oncommand="Cmds.showDevicePrefs()"/>
<command id="cmd_showSettings" label="&runtimeMenu_showSettings_label;" oncommand="Cmds.showSettings()"/>
<command id="cmd_removeProject" oncommand="Cmds.removeProject()" label="&projectMenu_remove_label;"/>
<command id="cmd_showProjectPanel" oncommand="Cmds.showProjectPanel()"/>
<command id="cmd_showRuntimePanel" oncommand="Cmds.showRuntimePanel()"/>
@ -83,6 +84,7 @@
<menuitem command="cmd_showPermissionsTable" accesskey="&runtimeMenu_showPermissionTable_accesskey;"/>
<menuitem command="cmd_showRuntimeDetails" accesskey="&runtimeMenu_showDetails_accesskey;"/>
<menuitem command="cmd_showDevicePrefs" accesskey="&runtimeMenu_showDevicePrefs_accesskey;"/>
<menuitem command="cmd_showSettings" accesskey="&runtimeMenu_showSettings_accesskey;"/>
<menuseparator/>
<menuitem command="cmd_disconnectRuntime" accesskey="&runtimeMenu_disconnect_accesskey;"/>
</menupopup>
@ -179,6 +181,7 @@
<toolbarbutton class="panel-item" id="runtime-details" command="cmd_showRuntimeDetails"/>
<toolbarbutton class="panel-item" id="runtime-permissions" command="cmd_showPermissionsTable"/>
<toolbarbutton class="panel-item" id="runtime-preferences" command="cmd_showDevicePrefs"/>
<toolbarbutton class="panel-item" id="runtime-preferences" command="cmd_showSettings"/>
<toolbarbutton class="panel-item" id="runtime-screenshot" command="cmd_takeScreenshot"/>
<toolbarbutton class="panel-item" id="runtime-disconnect" command="cmd_disconnectRuntime"/>
</vbox>
@ -197,6 +200,7 @@
<iframe id="deck-panel-runtimedetails" flex="1" src="runtimedetails.xhtml"/>
<iframe id="deck-panel-monitor" flex="1" lazysrc="monitor.xhtml"/>
<iframe id="deck-panel-devicepreferences" flex="1" src="devicepreferences.xhtml"/>
<iframe id="deck-panel-devicesettings" flex="1" src="devicesettings.xhtml"/>
</deck>
<splitter hidden="true" class="devtools-horizontal-splitter" orient="vertical"/>
<!-- toolbox iframe will be inserted here -->

View File

@ -18,6 +18,7 @@ const {ConnectionManager, Connection} = require("devtools/client/connection-mana
const {AppActorFront} = require("devtools/app-actor-front");
const {getDeviceFront} = require("devtools/server/actors/device");
const {getPreferenceFront} = require("devtools/server/actors/preference");
const {getSettingsFront} = require("devtools/server/actors/settings");
const {setTimeout} = require("sdk/timers");
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
const {RuntimeScanners, RuntimeTypes} = require("devtools/webide/runtimes");
@ -415,6 +416,13 @@ let AppManager = exports.AppManager = {
return getPreferenceFront(this.connection.client, this._listTabsResponse);
},
get settingsFront() {
if (!this._listTabsResponse) {
return null;
}
return getSettingsFront(this.connection.client, this._listTabsResponse);
},
disconnectRuntime: function() {
if (!this.connected) {
return promise.resolve();

View File

@ -0,0 +1,360 @@
/* 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/. */
const {Cu} = require("chrome");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm");
const EventEmitter = require("devtools/toolkit/event-emitter");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties");
let ConfigView;
module.exports = ConfigView = function(window) {
EventEmitter.decorate(this);
this._doc = window.document;
this._keys = [];
return this;
};
ConfigView.prototype = {
_renderByType: function(input, name, value, customType) {
value = customType || typeof value;
switch (value) {
case "boolean":
input.setAttribute("data-type", "boolean");
input.setAttribute("type", "checkbox");
break;
case "number":
input.setAttribute("data-type", "number");
input.setAttribute("type", "number");
break;
case "object":
input.setAttribute("data-type", "object");
input.setAttribute("type", "text");
break;
default:
input.setAttribute("data-type", "string");
input.setAttribute("type", "text");
break;
}
return input;
},
set front(front) {
this._front = front;
},
set keys(keys) {
this._keys = keys;
},
get keys() {
return this._keys;
},
set kind(kind) {
this._kind = kind;
},
set includeTypeName(include) {
this._includeTypeName = include;
},
search: function(event) {
if (event.target.value.length) {
let stringMatch = new RegExp(event.target.value, "i");
for (let i = 0; i < this._keys.length; i++) {
let key = this._keys[i];
let row = this._doc.getElementById("row-" + key);
if (key.match(stringMatch)) {
row.classList.remove("hide");
} else if (row) {
row.classList.add("hide");
}
}
} else {
var trs = this._doc.getElementById("device-fields").querySelectorAll("tr");
for (let i = 0; i < trs.length; i++) {
trs[i].classList.remove("hide");
}
}
},
generateField: function(name, value, hasUserValue, customType, newRow) {
let table = this._doc.querySelector("table");
let sResetDefault = Strings.GetStringFromName("device_reset_default");
if (this._keys.indexOf(name) === -1) {
this._keys.push(name);
}
let input = this._doc.createElement("input");
let tr = this._doc.createElement("tr");
tr.setAttribute("id", "row-" + name);
tr.classList.add("edit-row");
let td = this._doc.createElement("td");
td.classList.add("field-name");
td.textContent = name;
tr.appendChild(td);
td = this._doc.createElement("td");
input.classList.add("editable");
input.setAttribute("id", name);
input = this._renderByType(input, name, value, customType);
if (customType === "boolean" || input.type === "checkbox") {
input.checked = value;
} else {
if (typeof value === "object") {
value = JSON.stringify(value);
}
input.value = value;
}
td.appendChild(input);
tr.appendChild(td);
td = this._doc.createElement("td");
td.setAttribute("id", "td-" + name);
let button = this._doc.createElement("button");
button.setAttribute("data-id", name);
button.setAttribute("id", "btn-" + name);
button.classList.add("reset");
button.textContent = sResetDefault;
td.appendChild(button);
if (!hasUserValue) {
button.classList.add("hide");
}
tr.appendChild(td);
// If this is a new field, add it to the top of the table.
if (newRow) {
let existing = table.querySelector("#" + name);
if (!existing) {
table.insertBefore(tr, newRow);
} else {
existing.value = value;
}
} else {
table.appendChild(tr);
}
},
resetTable: function() {
let table = this._doc.querySelector("table");
let trs = table.querySelectorAll("tr:not(#add-custom-field)");
for (var i = 0; i < trs.length; i++) {
table.removeChild(trs[i]);
}
return table;
},
_getCallType: function(type, name) {
let frontName = "get";
if (this._includeTypeName) {
frontName += type;
}
return this._front[frontName + this._kind](name);
},
_setCallType: function(type, name, value) {
let frontName = "set";
if (this._includeTypeName) {
frontName += type;
}
return this._front[frontName + this._kind](name, value);
},
_saveByType: function(options) {
let fieldName = options.id;
let inputType = options.type;
let value = options.value;
let input = this._doc.getElementById(fieldName);
switch(inputType) {
case "boolean":
this._setCallType("Bool", fieldName, input.checked);
break;
case "number":
this._setCallType("Int", fieldName, value);
break;
case "object":
try {
value = JSON.parse(value);
} catch(e) {}
this._setCallType("Object", fieldName, value);
break;
default:
this._setCallType("Char", fieldName, value);
break;
}
},
updateField: function(event) {
if (event.target) {
let inputType = event.target.getAttribute("data-type");
let inputValue = event.target.checked || event.target.value;
if (event.target.nodeName == "input" &&
event.target.validity.valid &&
event.target.classList.contains("editable")) {
let id = event.target.id;
if (inputType === "boolean") {
if (event.target.checked) {
inputValue = true;
} else {
inputValue = false;
}
}
this._saveByType({
id: id,
type: inputType,
value: inputValue
});
this._doc.getElementById("btn-" + id).classList.remove("hide");
}
}
},
_resetToDefault: function(name, input, button) {
this._front["clearUser" + this._kind](name);
let dataType = input.getAttribute("data-type");
let tr = this._doc.getElementById("row-" + name);
switch (dataType) {
case "boolean":
this._defaultField = this._getCallType("Bool", name);
this._defaultField.then(boolean => {
input.checked = boolean;
}, () => {
input.checked = false;
tr.parentNode.removeChild(tr);
});
break;
case "number":
this._defaultField = this._getCallType("Int", name);
this._defaultField.then(number => {
input.value = number;
}, () => {
tr.parentNode.removeChild(tr);
});
break;
case "object":
this._defaultField = this._getCallType("Object", name);
this._defaultField.then(object => {
input.value = JSON.stringify(object);
}, () => {
tr.parentNode.removeChild(tr);
});
break;
default:
this._defaultField = this._getCallType("Char", name);
this._defaultField.then(string => {
input.value = string;
}, () => {
tr.parentNode.removeChild(tr);
});
break;
}
button.classList.add("hide");
},
checkReset: function(event) {
if (event.target.classList.contains("reset")) {
let btnId = event.target.getAttribute("data-id");
let input = this._doc.getElementById(btnId);
this._resetToDefault(btnId, input, event.target);
}
},
updateFieldType: function() {
let table = this._doc.querySelector("table");
let customValueType = table.querySelector("#custom-value-type").value;
let customTextEl = table.querySelector("#custom-value-text");
let customText = customTextEl.value;
if (customValueType.length === 0) {
return false;
}
switch (customValueType) {
case "boolean":
customTextEl.type = "checkbox";
customText = customTextEl.checked;
break;
case "number":
customText = parseInt(customText, 10) || 0;
customTextEl.type = "number";
break;
default:
customTextEl.type = "text";
break;
}
return customValueType;
},
clearNewFields: function() {
let table = this._doc.querySelector("table");
let customTextEl = table.querySelector("#custom-value-text");
if (customTextEl.checked) {
customTextEl.checked = false;
} else {
customTextEl.value = "";
}
this.updateFieldType();
},
updateNewField: function() {
let table = this._doc.querySelector("table");
let customValueType = this.updateFieldType();
if (!customValueType) {
return;
}
let customRow = table.querySelector("tr:nth-of-type(2)");
let customTextEl = table.querySelector("#custom-value-text");
let customTextNameEl = table.querySelector("#custom-value-name");
if (customTextEl.validity.valid) {
let customText = customTextEl.value;
if (customValueType === "boolean") {
customText = customTextEl.checked;
}
let customTextName = customTextNameEl.value.replace(/[^A-Za-z0-9\.\-_]/gi, "");
this.generateField(customTextName, customText, true, customValueType, customRow);
this._saveByType({
id: customTextName,
type: customValueType,
value: customText
});
customTextNameEl.value = "";
this.clearNewFields();
}
},
checkNewFieldSubmit: function(event) {
if (event.keyCode === 13) {
this._doc.getElementById("custom-value").click();
}
}
};

View File

@ -17,8 +17,9 @@ EXTRA_JS_MODULES.devtools.webide += [
'modules/addons.js',
'modules/app-manager.js',
'modules/build.js',
'modules/config-view.js',
'modules/remote-resources.js',
'modules/runtimes.js',
'modules/tab-store.js',
'modules/utils.js',
'modules/utils.js'
]

View File

@ -32,6 +32,7 @@ support-files =
build_app_windows2/manifest.webapp
build_app_windows2/package.json
build_app_windows2/stage/empty-directory
device_front_shared.js
head.js
hosted_app.manifest
templates.json
@ -47,6 +48,7 @@ support-files =
[test_autoconnect_runtime.html]
[test_telemetry.html]
[test_device_preferences.html]
[test_device_settings.html]
[test_fullscreenToolbox.html]
[test_zoom.html]
[test_build.html]

View File

@ -0,0 +1,219 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let customName;
let customValue;
let customValueType;
let customBtn;
let newField;
let change;
let doc;
let iframe;
let resetBtn;
let found = false;
function setDocument(frame) {
iframe = frame;
doc = iframe.contentWindow.document;
}
function fieldChange(fields, id) {
// Trigger existing field change
for (let field of fields) {
if (field.id == id) {
let button = doc.getElementById("btn-" + id);
found = true;
ok(button.classList.contains("hide"), "Default field detected");
field.value = "custom";
field.click();
ok(!button.classList.contains("hide"), "Custom field detected");
break;
}
}
ok(found, "Found " + id + " line");
}
function addNewField() {
found = false;
customName = doc.querySelector("#custom-value-name");
customValue = doc.querySelector("#custom-value-text");
customValueType = doc.querySelector("#custom-value-type");
customBtn = doc.querySelector("#custom-value");
change = doc.createEvent("HTMLEvents");
change.initEvent("change", false, true);
// Add a new custom string
customValueType.value = "string";
customValueType.dispatchEvent(change);
customName.value = "new-string-field!";
customValue.value = "test";
customBtn.click();
let newField = doc.querySelector("#new-string-field");
if (newField) {
found = true;
is(newField.type, "text", "Custom type is a string");
is(newField.value, "test", "Custom string new value is correct");
}
ok(found, "Found new string field line");
is(customName.value, "", "Custom string name reset");
is(customValue.value, "", "Custom string value reset");
}
function addNewFieldWithEnter() {
// Add a new custom value with the <enter> key
found = false;
customName.value = "new-string-field-two";
customValue.value = "test";
let newAddField = doc.querySelector("#add-custom-field");
let enter = doc.createEvent("KeyboardEvent");
enter.initKeyEvent(
"keyup", true, true, null, false, false, false, false, 13, 0);
newAddField.dispatchEvent(enter);
newField = doc.querySelector("#new-string-field-two");
if (newField) {
found = true;
is(newField.type, "text", "Custom type is a string");
is(newField.value, "test", "Custom string new value is correct");
}
ok(found, "Found new string field line");
is(customName.value, "", "Custom string name reset");
is(customValue.value, "", "Custom string value reset");
}
function editExistingField() {
// Edit existing custom string preference
newField.value = "test2";
newField.click();
is(newField.value, "test2", "Custom string existing value is correct");
}
function addNewFieldInteger() {
// Add a new custom integer preference with a valid integer
customValueType.value = "number";
customValueType.dispatchEvent(change);
customName.value = "new-integer-field";
customValue.value = 1;
found = false;
customBtn.click();
newField = doc.querySelector("#new-integer-field");
if (newField) {
found = true;
is(newField.type, "number", "Custom type is a number");
is(newField.value, 1, "Custom integer value is correct");
}
ok(found, "Found new integer field line");
is(customName.value, "", "Custom integer name reset");
is(customValue.value, 0, "Custom integer value reset");
}
let editFieldInteger = Task.async(function*() {
// Edit existing custom integer preference
newField.value = 3;
newField.click();
is(newField.value, 3, "Custom integer existing value is correct");
// Reset a custom field
let resetBtn = doc.querySelector("#btn-new-integer-field");
resetBtn.click();
try {
yield iframe.contentWindow.configView._defaultField;
} catch(err) {
let fieldRow = doc.querySelector("#row-new-integer-field");
if (!fieldRow) {
found = false;
}
ok(!found, "Custom field removed");
}
});
let resetExistingField = Task.async(function*(id) {
let existing = doc.getElementById(id);
existing.click();
is(existing.checked, false, "Existing boolean value is correct");
resetBtn = doc.getElementById("btn-" + id);
resetBtn.click();
yield iframe.contentWindow.configView._defaultField;
ok(resetBtn.classList.contains("hide"), true, "Reset button hidden");
is(existing.checked, true, "Existing field reset");
});
let resetNewField = Task.async(function*(id) {
let custom = doc.getElementById(id);
custom.click();
is(custom.value, "test", "New string value is correct");
resetBtn = doc.getElementById("btn-" + id);
resetBtn.click();
yield iframe.contentWindow.configView._defaultField;
ok(resetBtn.classList.contains("hide"), true, "Reset button hidden");
});
function addNewFieldBoolean() {
customValueType.value = "boolean";
customValueType.dispatchEvent(change);
customName.value = "new-boolean-field";
customValue.checked = true;
found = false;
customBtn.click();
newField = doc.querySelector("#new-boolean-field");
if (newField) {
found = true;
is(newField.type, "checkbox", "Custom type is a checkbox");
is(newField.checked, true, "Custom boolean value is correctly true");
}
ok(found, "Found new boolean field line");
// Mouse event trigger
var mouseClick = new MouseEvent("click", {
canBubble: true,
cancelable: true,
view: doc.parent,
});
found = false;
customValueType.value = "boolean";
customValueType.dispatchEvent(change);
customName.value = "new-boolean-field2";
customValue.dispatchEvent(mouseClick);
customBtn.dispatchEvent(mouseClick);
newField = doc.querySelector("#new-boolean-field2");
if (newField) {
found = true;
is(newField.checked, true, "Custom boolean value is correctly false");
}
ok(found, "Found new second boolean field line");
is(customName.value, "", "Custom boolean name reset");
is(customValue.checked, false, "Custom boolean value reset");
newField.click();
is(newField.checked, false, "Custom boolean existing value is correct");
}
function searchFields(deck, keyword) {
// Search for a non-existent field
let searchField = doc.querySelector("#search-bar");
searchField.value = "![o_O]!";
searchField.click();
let fieldsTotal = doc.querySelectorAll("tr.edit-row").length;
let hiddenFields = doc.querySelectorAll("tr.hide");
is(hiddenFields.length, fieldsTotal, "Search keyword not found");
// Search for existing fields
searchField.value = keyword;
searchField.click();
hiddenFields = doc.querySelectorAll("tr.hide");
isnot(hiddenFields.length, fieldsTotal, "Search keyword found");
doc.querySelector("#close").click();
ok(!deck.selectedPanel, "No panel selected");
}

View File

@ -8,6 +8,7 @@
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript;version=1.8" src="head.js"></script>
<script type="application/javascript;version=1.8" src="device_front_shared.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
@ -49,190 +50,34 @@
yield prefIframe.contentWindow.getAllPrefs;
yield nextTick();
let doc = prefIframe.contentWindow.document;
setDocument(prefIframe);
let fields = doc.querySelectorAll(".editable");
let preference = "accessibility.blockautorefresh";
let found = false;
// Trigger existing field change
for (let field of fields) {
if (field.id == preference) {
let button = doc.getElementById("btn-" + preference);
found = true;
ok(button.classList.contains("hide"), "Default field detected");
field.value = "custom";
field.click();
ok(!button.classList.contains("hide"), "Custom field detected");
break;
}
}
ok(found, "Found accessibility preference line");
fieldChange(fields, preference);
// Add new preferences
found = false;
let customName = doc.querySelector("#custom-value-name");
let customValue = doc.querySelector("#custom-value-text");
let customValueType = doc.querySelector("#custom-value-type");
let customBtn = doc.querySelector("#custom-value");
let change = doc.createEvent("HTMLEvents");
change.initEvent("change", false, true);
addNewField();
// Add a new custom string preference
customValueType.value = "string";
customValueType.dispatchEvent(change);
customName.value = "new-string-pref!";
customValue.value = "test";
customBtn.click();
let newPref = doc.querySelector("#new-string-pref");
if (newPref) {
found = true;
is(newPref.type, "text", "Custom type is a string");
is(newPref.value, "test", "Custom string new value is correct");
}
ok(found, "Found new string preference line");
is(customName.value, "", "Custom string name reset");
is(customValue.value, "", "Custom string value reset");
addNewFieldWithEnter();
// Add a new custom value with the <enter> key
found = false;
customName.value = "new-string-pref-two";
customValue.value = "test";
let newField = doc.querySelector("#add-custom-preference");
let enter = doc.createEvent("KeyboardEvent");
enter.initKeyEvent(
"keyup", true, true, null, false, false, false, false, 13, 0);
newField.dispatchEvent(enter);
newPref = doc.querySelector("#new-string-pref-two");
if (newPref) {
found = true;
is(newPref.type, "text", "Custom type is a string");
is(newPref.value, "test", "Custom string new value is correct");
}
ok(found, "Found new string preference line");
is(customName.value, "", "Custom string name reset");
is(customValue.value, "", "Custom string value reset");
editExistingField();
// Edit existing custom string preference
newPref.value = "test2";
newPref.click();
is(newPref.value, "test2", "Custom string existing value is correct");
addNewFieldInteger();
// Add a new custom integer preference with a valid integer
customValueType.value = "number";
customValueType.dispatchEvent(change);
customName.value = "new-integer-pref";
customValue.value = 1;
found = false;
yield editFieldInteger();
customBtn.click();
newPref = doc.querySelector("#new-integer-pref");
if (newPref) {
found = true;
is(newPref.type, "number", "Custom type is a number");
is(newPref.value, 1, "Custom integer value is correct");
}
ok(found, "Found new integer preference line");
is(customName.value, "", "Custom integer name reset");
is(customValue.value, 0, "Custom integer value reset");
yield resetExistingField("accessibility.accesskeycausesactivation");
// Edit existing custom integer preference
newPref.value = 3;
newPref.click();
is(newPref.value, 3, "Custom integer existing value is correct");
addNewFieldBoolean();
// Reset a custom preference
let resetBtn = doc.querySelector("#btn-new-integer-pref");
resetBtn.click();
try {
yield prefIframe.contentWindow.defaultPref;
} catch(err) {
let prefRow = doc.querySelector("#row-new-integer-pref");
if (!prefRow) {
found = false;
}
ok(!found, "Custom preference removed");
}
// Reset an existing preference
let existingPref = doc.getElementById("accessibility.accesskeycausesactivation");
existingPref.click();
is(existingPref.checked, false, "Existing boolean value is correct");
resetBtn = doc.getElementById("btn-accessibility.accesskeycausesactivation");
resetBtn.click();
yield prefIframe.contentWindow.defaultPref;
ok(resetBtn.classList.contains("hide"), true, "Reset button hidden");
is(existingPref.checked, true, "Existing preference reset");
// Add a new custom boolean preference
customValueType.value = "boolean";
customValueType.dispatchEvent(change);
customName.value = "new-boolean-pref";
customValue.checked = true;
found = false;
customBtn.click();
newPref = doc.querySelector("#new-boolean-pref");
if (newPref) {
found = true;
is(newPref.type, "checkbox", "Custom type is a checkbox");
is(newPref.checked, true, "Custom boolean value is correctly true");
}
ok(found, "Found new boolean preference line");
// Mouse event trigger
var mouseClick = new MouseEvent("click", {
canBubble: true,
cancelable: true,
view: doc.parent,
});
found = false;
customValueType.value = "boolean";
customValueType.dispatchEvent(change);
customName.value = "new-boolean-pref2";
customValue.dispatchEvent(mouseClick);
customBtn.dispatchEvent(mouseClick);
newPref = doc.querySelector("#new-boolean-pref2");
if (newPref) {
found = true;
is(newPref.checked, true, "Custom boolean value is correctly false");
}
ok(found, "Found new second boolean preference line");
is(customName.value, "", "Custom boolean name reset");
is(customValue.checked, false, "Custom boolean value reset");
// Edit existing custom boolean preference
newPref.click();
is(newPref.checked, false, "Custom boolean existing value is correct");
// Search for a non-existent field
let searchField = doc.querySelector("#search-bar");
searchField.value = "![o_O]!";
searchField.click();
let preferencesTotal = doc.querySelectorAll("tr.edit-row").length;
let hiddenPreferences = doc.querySelectorAll("tr.hide");
is(hiddenPreferences.length, preferencesTotal, "Search keyword not found");
// Search for existing fields
searchField.value = "debugger";
searchField.click();
hiddenPreferences = doc.querySelectorAll("tr.hide");
isnot(hiddenPreferences.length, preferencesTotal, "Search keyword found");
doc.querySelector("#close").click();
ok(!deck.selectedPanel, "No panel selected");
searchFields(deck, "debugger");
DebuggerServer.destroy();
yield closeWebIDE(win);
SimpleTest.finish();
}).then(null, e => {
ok(false, "Exception: " + e);
SimpleTest.finish();

View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8">
<title></title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript;version=1.8" src="head.js"></script>
<script type="application/javascript;version=1.8" src="device_front_shared.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<script type="application/javascript;version=1.8">
window.onload = function() {
SimpleTest.waitForExplicitFinish();
Task.spawn(function*() {
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
if (SpecialPowers.isMainProcess()) {
Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
}
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
let win = yield openWebIDE();
let settingIframe = win.document.querySelector("#deck-panel-devicesettings");
yield documentIsLoaded(settingIframe.contentWindow.document);
win.AppManager.update("runtimelist");
yield connectToLocalRuntime(win);
yield nextTick();
let settings = win.document.querySelector("#cmd_showSettings");
ok(!settings.hasAttribute("disabled"), "device settings cmd enabled");
let deck = win.document.querySelector("#deck");
win.Cmds.showSettings();
is(deck.selectedPanel, settingIframe, "device settings iframe selected");
yield settingIframe.contentWindow.getAllSettings;
yield nextTick();
setDocument(settingIframe);
let fields = doc.querySelectorAll(".editable");
addNewField();
addNewFieldWithEnter();
editExistingField();
addNewFieldInteger();
yield editFieldInteger();
yield resetNewField("new-string-field");
addNewFieldBoolean();
searchFields(deck, "new-boolean-field2");
DebuggerServer.destroy();
yield closeWebIDE(win);
SimpleTest.finish();
}).then(null, e => {
ok(false, "Exception: " + e);
SimpleTest.finish();
});
}
</script>
</body>
</html>

View File

@ -14,7 +14,7 @@ html, body {
display: none;
}
#device-preferences {
#device-fields {
font-family: sans-serif;
padding-left: 6px;
width: 100%;
@ -37,31 +37,34 @@ header {
padding: 10px 20px;
}
#device-preferences td {
#device-fields td {
background-color: #f1f1f1;
border-bottom: 1px solid #ccc;
border-right: 1px solid #fff;
width: 33.3%;
}
#device-preferences td.preference-name {
#device-fields td:first-child {
min-width: 400px;
}
#device-fields td.preference-name, #device-fields td.setting-name {
width: 50%;
min-width: 400px;
word-break: break-all;
}
#device-preferences button {
#device-fields button {
display: inline-block;
font-family: sans-serif;
font-size: 0.7rem;
white-space: nowrap;
}
#device-preferences tr.hide, #device-preferences button.hide {
#device-fields tr.hide, #device-fields button.hide {
display: none;
}
#device-preferences .custom-input {
#device-fields .custom-input {
width: 300px;
}

View File

@ -15,4 +15,4 @@ webide.jar:
skin/runtimedetails.css (runtimedetails.css)
skin/permissionstable.css (permissionstable.css)
skin/monitor.css (monitor.css)
skin/devicepreferences.css (devicepreferences.css)
skin/config-view.css (config-view.css)

View File

@ -41,6 +41,8 @@
<!ENTITY runtimeMenu_showMonitor_accesskey "M">
<!ENTITY runtimeMenu_showDevicePrefs_label "Device Preferences">
<!ENTITY runtimeMenu_showDevicePrefs_accesskey "D">
<!ENTITY runtimeMenu_showSettings_label "Device Settings">
<!ENTITY runtimeMenu_showSettings_accesskey "s">
<!ENTITY viewMenu_label "View">
<!ENTITY viewMenu_accesskey "V">
@ -147,16 +149,26 @@
<!ENTITY runtimedetails_requestPrivileges "request higher privileges">
<!ENTITY runtimedetails_privilegesWarning "(Will reboot device. Requires root access.)">
<!-- Device Preferences and Settings -->
<!ENTITY device_typeboolean "Boolean">
<!ENTITY device_typenumber "Integer">
<!ENTITY device_typestring "String">
<!ENTITY device_typeobject "Object">
<!ENTITY device_typenone "Select a type">
<!-- Device Preferences -->
<!ENTITY devicepreferences_title "Device Preferences">
<!ENTITY devicepreferences_search "Search preferences">
<!ENTITY devicepreferences_newname "New preference name">
<!ENTITY devicepreferences_newtext "Preference value">
<!ENTITY devicepreferences_addnew "Add new preference">
<!ENTITY devicepreferences_typeboolean "Boolean">
<!ENTITY devicepreferences_typenumber "Integer">
<!ENTITY devicepreferences_typestring "String">
<!ENTITY devicepreferences_typenone "Select a type">
<!ENTITY devicepreference_title "Device Preferences">
<!ENTITY devicepreference_search "Search preferences">
<!ENTITY devicepreference_newname "New preference name">
<!ENTITY devicepreference_newtext "Preference value">
<!ENTITY devicepreference_addnew "Add new preference">
<!-- Device Settings -->
<!ENTITY devicesetting_title "Device Settings">
<!ENTITY devicesetting_search "Search settings">
<!ENTITY devicesetting_newname "New setting name">
<!ENTITY devicesetting_newtext "Setting value">
<!ENTITY devicesetting_addnew "Add new setting">
<!-- Monitor -->
<!ENTITY monitor_title "Monitor">

View File

@ -75,5 +75,5 @@ status_warning=WARNINGS
status_error=ERRORS
status_unknown=UNKNOWN
# Preferences
devicepreferences_reset_default=Reset to default
# Device preferences and settings
device_reset_default=Reset to default

View File

@ -43,6 +43,13 @@ body {
}
}
/* Disabled playerWidget when the animation has ended */
.finished {
pointer-events: none;
opacity: .5;
}
/* Animation title gutter, contains the name, duration, iteration */
.animation-title {
@ -91,7 +98,8 @@ body {
background-image: url(debugger-pause.png);
}
.paused .timeline .toggle::before {
.paused .timeline .toggle::before,
.finished .timeline .toggle::before {
background-image: url(debugger-play.png);
}
@ -100,7 +108,8 @@ body {
background-image: url(debugger-pause@2x.png);
}
.paused .timeline .toggle::before {
.paused .timeline .toggle::before,
.finished .timeline .toggle::before {
background-image: url(debugger-play@2x.png);
}
}

View File

@ -491,7 +491,8 @@ RtspMediaResource::RtspMediaResource(MediaDecoder* aDecoder,
nsIChannel* aChannel, nsIURI* aURI, const nsACString& aContentType)
: BaseMediaResource(aDecoder, aChannel, aURI, aContentType)
, mIsConnected(false)
, mRealTime(false)
, mIsLiveStream(false)
, mHasTimestamp(true)
, mIsSuspend(true)
{
#ifndef NECKO_PROTOCOL_rtsp
@ -639,9 +640,6 @@ RtspMediaResource::OnMediaDataAvailable(uint8_t aTrackIdx,
uint32_t frameType;
meta->GetTimeStamp(&time);
meta->GetFrameType(&frameType);
if (mRealTime) {
time = 0;
}
mTrackBuffer[aTrackIdx]->WriteBuffer(data.BeginReading(), length, time,
frameType);
return NS_OK;
@ -727,7 +725,7 @@ RtspMediaResource::OnConnected(uint8_t aTrackIdx,
// If the durationUs is 0, imply the stream is live stream.
if (durationUs) {
// Not live stream.
mRealTime = false;
mIsLiveStream = false;
mDecoder->SetInfinite(false);
mDecoder->SetDuration((double)(durationUs) / USECS_PER_S);
} else {
@ -740,7 +738,7 @@ RtspMediaResource::OnConnected(uint8_t aTrackIdx,
NS_DispatchToMainThread(event);
return NS_ERROR_FAILURE;
} else {
mRealTime = true;
mIsLiveStream = true;
bool seekable = false;
mDecoder->SetInfinite(true);
mDecoder->SetMediaSeekable(seekable);

View File

@ -92,8 +92,10 @@ public:
return mMediaStreamController;
}
// Even it is a live stream, as long as it provides valid timestamps,
// we tell state machine it's not a live stream.
virtual bool IsRealTime() MOZ_OVERRIDE {
return mRealTime;
return !mHasTimestamp;
}
// Called by RtspOmxReader, dispatch a runnable to notify mDecoder.
@ -154,7 +156,7 @@ public:
virtual double GetDownloadRate(bool* aIsReliable) MOZ_OVERRIDE { *aIsReliable = false; return 0; }
virtual int64_t GetLength() MOZ_OVERRIDE {
if (mRealTime) {
if (mIsLiveStream) {
return -1;
}
return 0;
@ -247,8 +249,10 @@ private:
// A flag that indicates the |RtspMediaResource::OnConnected| has already been
// called.
bool mIsConnected;
// live stream
bool mRealTime;
// Whether it's a live stream.
bool mIsLiveStream;
// Whether it provides timestamps.
bool mHasTimestamp;
// Indicate the rtsp controller is suspended or not. Main thread only.
bool mIsSuspend;
};

View File

@ -134,7 +134,7 @@ NfcContentHelper.prototype = {
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (inParent) {
this._tabId = -1;
this._tabId = Ci.nsINfcBrowserAPI.SYSTEM_APP_ID;
} else {
throw Components.Exception("Can't get tab id in child process",
Cr.NS_ERROR_UNEXPECTED);

View File

@ -181,7 +181,7 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
this.focusApp = id;
} else if (this.focusApp == id){
// Set focusApp to null means currently there is no foreground app.
this.focusApp = null;
this.focusApp = NFC.SYSTEM_APP_ID;
}
},
@ -228,27 +228,30 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function () {
},
onTagFound: function onTagFound(message) {
let target = this.eventListeners[this.focusApp] ||
this.eventListeners[NFC.SYSTEM_APP_ID];
message.event = NFC.TAG_EVENT_FOUND;
for (let id in this.eventListeners) {
this.notifyDOMEvent(this.eventListeners[id], message);
}
this.notifyDOMEvent(target, message);
delete message.event;
},
onTagLost: function onTagLost(sessionToken) {
for (let id in this.eventListeners) {
this.notifyDOMEvent(this.eventListeners[id],
{ event: NFC.TAG_EVENT_LOST,
sessionToken: sessionToken });
}
let target = this.eventListeners[this.focusApp] ||
this.eventListeners[NFC.SYSTEM_APP_ID];
this.notifyDOMEvent(target, { event: NFC.TAG_EVENT_LOST,
sessionToken: sessionToken });
},
onPeerEvent: function onPeerEvent(eventType, sessionToken) {
for (let id in this.eventListeners) {
this.notifyDOMEvent(this.eventListeners[id],
{ event: eventType,
sessionToken: sessionToken });
}
let target = this.eventListeners[this.focusApp] ||
this.eventListeners[NFC.SYSTEM_APP_ID];
this.notifyDOMEvent(target, { event: eventType,
sessionToken: sessionToken });
},
onRFStateChange: function onRFStateChange(rfState) {

View File

@ -46,5 +46,8 @@ this.TAG_EVENT_LOST = 0x04;
this.PEER_EVENT_FOUND = 0x05;
this.RF_EVENT_STATE_CHANGE = 0x06;
// This value should sync with |SYSTEM_APP_ID| in nsINfcContentHelper.idl
this.SYSTEM_APP_ID = -1;
// Allow this file to be imported via Components.utils.import().
this.EXPORTED_SYMBOLS = Object.keys(this);

View File

@ -102,9 +102,11 @@ interface nsINfcRequestCallback : nsISupports
void notifyError(in DOMString errorMsg);
};
[scriptable, uuid(7ae46728-bc42-44bd-8093-bc7563abf52d)]
[scriptable, uuid(7ae94107-adc9-4467-ae44-72f9a91f3ee8)]
interface nsINfcBrowserAPI : nsISupports
{
const int32_t SYSTEM_APP_ID = -1;
void setFocusApp(in uint64_t tabId,
in boolean isFocus);
};

View File

@ -2954,7 +2954,7 @@ RilObject.prototype = {
GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
COMPREHENSIONTLV_FLAG_CR);
GsmPDUHelper.writeHexOctet(3);
if (response.command) {
if (command) {
GsmPDUHelper.writeHexOctet(command.commandNumber);
GsmPDUHelper.writeHexOctet(command.typeOfCommand);
GsmPDUHelper.writeHexOctet(command.commandQualifier);
@ -3010,52 +3010,46 @@ RilObject.prototype = {
// No need to process Text data if user requests help information.
if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
let text;
if (response.isYesNo !== undefined) {
// GET_INKEY
// When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
// ("Yes/No") command with command qualifier set to "Yes/No", it shall
// supply the value '01' when the answer is "positive" and the value
// '00' when the answer is "negative" in the Text string data object.
text = response.isYesNo ? String.fromCharCode(0x01)
: String.fromCharCode(0x00);
} else {
text = response.input;
}
if (text !== undefined) {
GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
COMPREHENSIONTLV_FLAG_CR);
// 2nd mark for text length
Buf.startCalOutgoingSize(function(size) {
// Text length is in number of hexOctets, which costs 4 uint8 per hexOctet.
GsmPDUHelper.writeHexOctet(size / 4);
});
let coding = command.options.isUCS2 ?
let coding = command.options.isUCS2 ?
STK_TEXT_CODING_UCS2 :
(command.options.isPacked ?
STK_TEXT_CODING_GSM_7BIT_PACKED :
STK_TEXT_CODING_GSM_8BIT);
if (response.isYesNo !== undefined) {
// Tag: GET_INKEY
// When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
// ("Yes/No") command with command qualifier set to "Yes/No", it shall
// supply the value '01' when the answer is "positive" and the value
// '00' when the answer is "negative" in the Text string data object.
GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
COMPREHENSIONTLV_FLAG_CR);
// Length: 2
GsmPDUHelper.writeHexOctet(2);
// Value: Coding, Yes/No.
GsmPDUHelper.writeHexOctet(coding);
// Write Text String.
switch (coding) {
case STK_TEXT_CODING_UCS2:
GsmPDUHelper.writeUCS2String(text);
break;
case STK_TEXT_CODING_GSM_7BIT_PACKED:
GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
break;
case STK_TEXT_CODING_GSM_8BIT:
for (let i = 0; i < text.length; i++) {
GsmPDUHelper.writeHexOctet(text.charCodeAt(i));
}
break;
GsmPDUHelper.writeHexOctet(response.isYesNo ? 0x01 : 0x00);
} else {
if (response.input !== undefined) {
ComprehensionTlvHelper.writeTextStringTlv(response.input, coding);
}
}
}
// Calculate and write text length to 2nd mark
Buf.stopCalOutgoingSize();
// Duration
if (response.resultCode === STK_RESULT_NO_RESPONSE_FROM_USER) {
// In TS102 223, 6.4.2 GET INKEY, "if the UICC requests a variable timeout,
// the terminal shall wait until either the user enters a single character
// or the timeout expires. The timer starts when the text is displayed on
// the screen and stops when the TERMINAL RESPONSE is sent. The terminal
// shall pass the total display text duration (command execution duration)
// to the UICC using the TERMINAL RESPONSE. The time unit of the response
// is identical to the time unit of the requested variable timeout."
let duration = command && command.options && command.options.duration;
if (duration) {
GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DURATION);
GsmPDUHelper.writeHexOctet(2);
GsmPDUHelper.writeHexOctet(duration.timeUnit);
GsmPDUHelper.writeHexOctet(duration.timeInterval);
}
}
@ -7205,6 +7199,36 @@ GsmPDUHelperObject.prototype = {
return array;
},
/**
* Helper to write data into a temporary buffer for easier length encoding when
* the number of octets for the length encoding is varied.
*
* @param writeFunction
* Function of how the data to be written into temporary buffer.
*
* @return array of written octets.
**/
writeWithBuffer: function(writeFunction) {
let buf = [];
let writeHexOctet = this.writeHexOctet;
this.writeHexOctet = function(octet) {
buf.push(octet);
}
try {
writeFunction();
} catch (e) {
if (DEBUG) {
debug("Error when writeWithBuffer: " + e);
}
buf = [];
} finally {
this.writeHexOctet = writeHexOctet;
}
return buf;
},
/**
* Convert an octet (number) to a BCD number.
*
@ -7557,6 +7581,28 @@ GsmPDUHelperObject.prototype = {
}
},
writeStringAs8BitUnpacked: function(text) {
const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
let len = text ? text.length : 0;
for (let i = 0; i < len; i++) {
let c = text.charAt(i);
let octet = langTable.indexOf(c);
if (octet == -1) {
octet = langShiftTable.indexOf(c);
if (octet == -1) {
// Fallback to ASCII space.
octet = langTable.indexOf(' ');
} else {
this.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
}
}
this.writeHexOctet(octet);
}
},
/**
* Read user data and decode as a UCS2 string.
*
@ -10389,9 +10435,10 @@ ICCPDUHelperObject.prototype = {
if (octet == -1) {
// Fallback to ASCII space.
octet = langTable.indexOf(' ');
} else {
GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
j++;
}
GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
j++;
}
GsmPDUHelper.writeHexOctet(octet);
j++;
@ -12166,6 +12213,40 @@ ComprehensionTlvHelperObject.prototype = {
GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60);
},
writeTextStringTlv: function(text, coding) {
let GsmPDUHelper = this.context.GsmPDUHelper;
let buf = GsmPDUHelper.writeWithBuffer(() => {
// Write Coding.
GsmPDUHelper.writeHexOctet(coding);
// Write Text String.
switch (coding) {
case STK_TEXT_CODING_UCS2:
GsmPDUHelper.writeUCS2String(text);
break;
case STK_TEXT_CODING_GSM_7BIT_PACKED:
GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
break;
case STK_TEXT_CODING_GSM_8BIT:
GsmPDUHelper.writeStringAs8BitUnpacked(text);
break;
}
});
let length = buf.length;
if (length) {
// Write Tag.
GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
COMPREHENSIONTLV_FLAG_CR);
// Write Length.
this.writeLength(length);
// Write Value.
for (let i = 0; i < length; i++) {
GsmPDUHelper.writeHexOctet(buf[i]);
}
}
},
getSizeOfLengthOctets: function(length) {
if (length >= 0x10000) {
return 4; // 0x83, len_1, len_2, len_3

View File

@ -227,6 +227,163 @@ add_test(function test_stk_terminal_response_get_input_empty_string() {
context.RIL.sendStkTerminalResponse(response);
});
/**
* Verify STK terminal response : GET INPUT with 160 unpacked characters.
*
* @See |TERMINAL RESPONSE: GET INPUT 1.8.1| of 27.22.4.3.1 GET INPUT (normal)
* in TS 102 384.
*/
add_test(function test_stk_terminal_response_get_input_160_unpacked_characters() {
let worker = newUint8SupportOutgoingIndexWorker();
let context = worker.ContextPool._contexts[0];
let buf = context.Buf;
let pduHelper = context.GsmPDUHelper;
let iccPduHelper = context.ICCPDUHelper;
let TEST_TEXT_STRING = "***1111111111###" +
"***2222222222###" +
"***3333333333###" +
"***4444444444###" +
"***5555555555###" +
"***6666666666###" +
"***7777777777###" +
"***8888888888###" +
"***9999999999###" +
"***0000000000###";
buf.sendParcel = function() {
// Type
do_check_eq(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
// Token : we don't care
this.readInt32();
// Data Size, 352 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
// TLV_DEVICE_ID_SIZE(4) +
// TLV_RESULT_SIZE(3) +
// TEXT LENGTH(164))
do_check_eq(this.readInt32(), 352);
// Command Details, Type-Length-Value
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
COMPREHENSIONTLV_FLAG_CR);
do_check_eq(pduHelper.readHexOctet(), 3);
do_check_eq(pduHelper.readHexOctet(), 0x01);
do_check_eq(pduHelper.readHexOctet(), STK_CMD_GET_INPUT);
do_check_eq(pduHelper.readHexOctet(), 0x00);
// Device Identifies, Type-Length-Value(Source ID-Destination ID)
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
do_check_eq(pduHelper.readHexOctet(), 2);
do_check_eq(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
do_check_eq(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
// Result
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
COMPREHENSIONTLV_FLAG_CR);
do_check_eq(pduHelper.readHexOctet(), 1);
do_check_eq(pduHelper.readHexOctet(), STK_RESULT_OK);
// Text
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_TEXT_STRING |
COMPREHENSIONTLV_FLAG_CR);
// C-TLV Length Encoding: 161 = 0x81 0xA1
do_check_eq(pduHelper.readHexOctet(), 0x81);
do_check_eq(pduHelper.readHexOctet(), 0xA1);
do_check_eq(pduHelper.readHexOctet(), STK_TEXT_CODING_GSM_8BIT);
do_check_eq(iccPduHelper.read8BitUnpackedToString(160), TEST_TEXT_STRING);
run_next_test();
};
let response = {
command: {
commandNumber: 0x01,
typeOfCommand: STK_CMD_GET_INPUT,
commandQualifier: 0x00,
options: {
minLength: 160,
maxLength: 160,
text: TEST_TEXT_STRING
}
},
input: TEST_TEXT_STRING,
resultCode: STK_RESULT_OK
};
context.RIL.sendStkTerminalResponse(response);
});
/**
* Verify STK terminal response : GET_INKEY - NO_RESPONSE_FROM_USER with
* duration provided.
*
* @See |27.22.4.2.8 GET INKEY (Variable Time out)| in TS 102 384.
*/
add_test(function test_stk_terminal_response_get_inkey_no_response_from_user() {
let worker = newUint8SupportOutgoingIndexWorker();
let context = worker.ContextPool._contexts[0];
let buf = context.Buf;
let pduHelper = context.GsmPDUHelper;
buf.sendParcel = function() {
// Type
do_check_eq(this.readInt32(), REQUEST_STK_SEND_TERMINAL_RESPONSE);
// Token : we don't care
this.readInt32();
// Data Size, 32 = 2 * (TLV_COMMAND_DETAILS_SIZE(5) +
// TLV_DEVICE_ID_SIZE(4) +
// TLV_RESULT_SIZE(3) +
// DURATION(4))
do_check_eq(this.readInt32(), 32);
// Command Details, Type-Length-Value
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
COMPREHENSIONTLV_FLAG_CR);
do_check_eq(pduHelper.readHexOctet(), 3);
do_check_eq(pduHelper.readHexOctet(), 0x01);
do_check_eq(pduHelper.readHexOctet(), STK_CMD_GET_INKEY);
do_check_eq(pduHelper.readHexOctet(), 0x00);
// Device Identifies, Type-Length-Value(Source ID-Destination ID)
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DEVICE_ID);
do_check_eq(pduHelper.readHexOctet(), 2);
do_check_eq(pduHelper.readHexOctet(), STK_DEVICE_ID_ME);
do_check_eq(pduHelper.readHexOctet(), STK_DEVICE_ID_SIM);
// Result
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_RESULT |
COMPREHENSIONTLV_FLAG_CR);
do_check_eq(pduHelper.readHexOctet(), 1);
do_check_eq(pduHelper.readHexOctet(), STK_RESULT_NO_RESPONSE_FROM_USER);
// Duration, Type-Length-Value(Time unit, Time interval)
do_check_eq(pduHelper.readHexOctet(), COMPREHENSIONTLV_TAG_DURATION);
do_check_eq(pduHelper.readHexOctet(), 2);
do_check_eq(pduHelper.readHexOctet(), STK_TIME_UNIT_SECOND);
do_check_eq(pduHelper.readHexOctet(), 10);
run_next_test();
};
let response = {
command: {
commandNumber: 0x01,
typeOfCommand: STK_CMD_GET_INKEY,
commandQualifier: 0x00,
options: {
duration: {
timeUnit: STK_TIME_UNIT_SECOND,
timeInterval: 10
},
text: 'Enter "+"'
}
},
resultCode: STK_RESULT_NO_RESPONSE_FROM_USER
};
context.RIL.sendStkTerminalResponse(response);
});
/**
* Verify STK terminal response : GET_INKEY - YES/NO request
*/

View File

@ -4,13 +4,27 @@
MARIONETTE_TIMEOUT = 60000;
MARIONETTE_HEAD_JS = 'head.js';
let connection;
const normalNumber = "0912345678";
const emergencyNumber = "112";
let outCall;
function setRadioEnabledAll(enabled) {
let promises = [];
let numOfSim = navigator.mozMobileConnections.length;
for (let i = 0; i < numOfSim; i++) {
let connection = navigator.mozMobileConnections[i];
ok(connection instanceof MozMobileConnection,
"connection[" + i + "] is instanceof " + connection.constructor);
promises.push(gSetRadioEnabled(connection, enabled));
}
return Promise.all(promises);
}
function testDial_NormalNumber() {
return gSetRadioEnabled(connection, false)
return setRadioEnabledAll(false)
.then(() => gDial(normalNumber))
.catch(cause => {
is(cause, "RadioNotAvailable");
@ -19,7 +33,7 @@ function testDial_NormalNumber() {
}
function testDial_EmergencyNumber() {
return gSetRadioEnabled(connection, false)
return setRadioEnabledAll(false)
.then(() => gDial(emergencyNumber))
.then(call => { outCall = call; })
.then(() => gRemoteAnswer(outCall))
@ -28,7 +42,7 @@ function testDial_EmergencyNumber() {
}
function testDialEmergency_NormalNumber() {
return gSetRadioEnabled(connection, false)
return setRadioEnabledAll(false)
.then(() => gDialEmergency(normalNumber))
.catch(cause => {
is(cause, "RadioNotAvailable");
@ -37,7 +51,7 @@ function testDialEmergency_NormalNumber() {
}
function testDialEmergency_EmergencyNumber() {
return gSetRadioEnabled(connection, false)
return setRadioEnabledAll(false)
.then(() => gDialEmergency(emergencyNumber))
.then(call => { outCall = call; })
.then(() => gRemoteAnswer(outCall))
@ -46,16 +60,12 @@ function testDialEmergency_EmergencyNumber() {
}
startTestWithPermissions(['mobileconnection'], function() {
connection = navigator.mozMobileConnections[0];
ok(connection instanceof MozMobileConnection,
"connection is instanceof " + connection.constructor);
Promise.resolve()
.then(() => testDial_NormalNumber())
.then(() => testDial_EmergencyNumber())
.then(() => testDialEmergency_NormalNumber())
.then(() => testDialEmergency_EmergencyNumber())
.then(() => gSetRadioEnabled(connection, true))
.then(() => setRadioEnabledAll(true))
.catch(error => ok(false, "Promise reject: " + error))
.then(finish);
});

View File

@ -152,8 +152,7 @@ public class BrowserApp extends GeckoApp
OnUrlOpenListener,
OnUrlOpenInBackgroundListener,
ActionModeCompat.Presenter,
LayoutInflater.Factory,
TopSitesPanel.BrowserTilesRecorderProvider {
LayoutInflater.Factory {
private static final String LOGTAG = "GeckoBrowserApp";
private static final int TABS_ANIMATION_DURATION = 450;
@ -260,8 +259,6 @@ public class BrowserApp extends GeckoApp
private final DynamicToolbar mDynamicToolbar = new DynamicToolbar();
private TilesRecorder mTilesRecorder;
@Override
public View onCreateView(final String name, final Context context, final AttributeSet attrs) {
final View view;
@ -663,8 +660,6 @@ public class BrowserApp extends GeckoApp
// Set the maximum bits-per-pixel the favicon system cares about.
IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth());
mTilesRecorder = new TilesRecorder();
}
private void setupSystemUITinting() {
@ -3364,9 +3359,4 @@ public class BrowserApp extends GeckoApp
appLocale,
previousSession);
}
@Override
public TilesRecorder getTilesRecorder() {
return mTilesRecorder;
}
}

View File

@ -128,22 +128,13 @@ public class TopSitesPanel extends HomeFragment {
}
}
public interface BrowserTilesRecorderProvider {
public TilesRecorder getTilesRecorder();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mMaxGridEntries = activity.getResources().getInteger(R.integer.number_of_top_sites);
try {
mTilesRecorder = ((BrowserTilesRecorderProvider) activity).getTilesRecorder();
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement TopSitesPanel.BrowserTilesRecorderProvider");
}
mTilesRecorder = new TilesRecorder();
}
@Override

View File

@ -14,6 +14,7 @@ import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.WindowUtils;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
@ -28,7 +29,6 @@ import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
@ -204,8 +204,7 @@ public class LightweightTheme implements GeckoEventListener {
}
// Get the max display dimension so we can crop or expand the theme.
DisplayMetrics dm = mApplication.getResources().getDisplayMetrics();
int maxWidth = Math.max(dm.widthPixels, dm.heightPixels);
final int maxWidth = WindowUtils.getLargestDimension(mApplication);
// The lightweight theme image's width and height.
final int bitmapWidth = bitmap.getWidth();

View File

@ -66,7 +66,9 @@ public class MenuPopup extends PopupWindow {
@Override
public void showAsDropDown(View anchor) {
// Set a height, so that the popup will not be displayed below the bottom of the screen.
setHeight(mPopupMinHeight);
// We use the exact height of the internal content, which is the technique described in
// http://stackoverflow.com/a/7698709
setHeight(mPanel.getHeight());
// Attempt to align the center of the popup with the center of the anchor. If the anchor is
// near the edge of the screen, the popup will just align with the edge of the screen.

View File

@ -84,6 +84,7 @@ gujar.sources += [
'util/ThreadUtils.java',
'util/UIAsyncTask.java',
'util/WebActivityMapper.java',
'util/WindowUtils.java',
]
gujar.extra_jars = [
'constants.jar',

View File

@ -226,16 +226,33 @@ class TabsGridLayout extends GridView
break;
case CLOSED:
if(mTabsAdapter.getCount() > 0) {
if (mTabsAdapter.getCount() > 0) {
animateRemoveTab(tab);
}
if (tab.isPrivate() == mIsPrivate && mTabsAdapter.getCount() > 0) {
if (mTabsAdapter.removeTab(tab)) {
int selected = mTabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
updateSelectedStyle(selected);
}
}
break;
final Tabs tabsInstance = Tabs.getInstance();
if (mTabsAdapter.removeTab(tab)) {
if (tab.isPrivate() == mIsPrivate && mTabsAdapter.getCount() > 0) {
int selected = mTabsAdapter.getPositionForTab(tabsInstance.getSelectedTab());
updateSelectedStyle(selected);
}
if(!tab.isPrivate()) {
// Make sure we always have at least one normal tab
final Iterable<Tab> tabs = tabsInstance.getTabsInOrder();
boolean removedTabIsLastNormalTab = true;
for (Tab singleTab : tabs) {
if (!singleTab.isPrivate()) {
removedTabIsLastNormalTab = false;
break;
}
}
if (removedTabIsLastNormalTab) {
tabsInstance.addTab();
}
}
}
break;
case SELECTED:
// Update the selected position, then fall through...

View File

@ -151,6 +151,10 @@ skip-if = android_version == "10"
[testSelectionHandler]
skip-if = android_version == "10"
# testInputSelections disabled on Android 2.3 by trailing skip-if, due to bug 980074
[testInputSelections]
skip-if = android_version == "10"
# testTextareaSelections disabled on Android 2.3 by trailing skip-if, due to bug 980074
[testTextareaSelections]
skip-if = android_version == "10"

View File

@ -0,0 +1,543 @@
<html>
<head>
<title>Automated RTL/LTR Text Selection tests for Input elements</title>
<meta name="viewport" content="initial-scale=1.0"/>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript">
// Used to create handle movement events for SelectionHandler.
const ANCHOR = "ANCHOR";
const FOCUS = "FOCUS";
// Types of DOM nodes that serve as Selection Anchor/Focus nodes.
const DIV_NODE = "DIV";
const TEXT_NODE = "#text";
// Used to specifiy midpoint selection text left/right of center.
const EST_SEL_TEXT_BOUND_CHARS = 5;
// Used to create test scenarios, and verify results.
const LTR_INPUT_TEXT_VALUE = "This input text is one character short of it's maxmimum.";
const RTL_INPUT_TEXT_VALUE = "טקסט קלט זה קצר תו אחד של זה גדול.";
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import('resource://gre/modules/Geometry.jsm');
/* =================================================================================
*
* Start of all text selection tests, check initialization state.
*/
function startTests() {
testLTR_selectAll().
then(testRTL_selectAll).
then(testLTR_dragFocusHandleToSelf).
then(testLTR_dragAnchorHandleToSelf).
then(testRTL_dragFocusHandleToSelf).
then(testRTL_dragAnchorHandleToSelf).
then(finishTests, function(err) {
ok(false, "Error in selection test " + err);
finishTests();
});
}
/* =================================================================================
*
* LTR selectAll() test selects the entire single-line <input> element and ensures:
* ) The Selection exists.
* ) The Selection text matches an expected value.
*
* ) Assumptions about the DOM Selection Anchor node are correct.
* ) Assumptions about the DOM Selection Focus node are correct.
*
* ) The UI Selection anchor handle is aligned vertically with the focus handle.
* ) The UI Selection anchor handle is left of the focus handle.
*/
function testLTR_selectAll() {
// Select entire LTR Input element.
var sh = getSelectionHandler();
var element = document.getElementById("LTRInput");
element.value = LTR_INPUT_TEXT_VALUE;
sh.startSelection(element);
var selection = sh._getSelection();
var anchorNode = selection.anchorNode;
var anchorOffset = selection.anchorOffset;
var focusNode = selection.focusNode;
var focusOffset = selection.focusOffset;
var anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
var focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
return Promise.all([
ok(sh.isSelectionActive(),
"testLTR_selectAll starts, selection should be active."),
is(sh._targetElement, element,
"LTR SelectionHandler reference is the node we provided."),
is(sh._getSelectedText(), LTR_INPUT_TEXT_VALUE,
"LTR Selection text should match expected value."),
isNot(anchorNode, element,
"LTR Selection Anchor isn't the LTRInput node."),
is(anchorNode.nodeName, DIV_NODE, "LTR Anchor node is a DIV node."),
ok(!document.contains(anchorNode), "LTR Anchor node is an anonymous DIV node."),
is(anchorNode.parentNode, element, "LTR Anchor node is a child of the LTRInput node."),
is(anchorOffset, 0,
"LTR Selection starts at Anchor node with offset 0."),
isNot(focusNode, element,
"LTR Selection Focus isn't the LTRInput node."),
is(focusNode.nodeName, TEXT_NODE, "LTR Focus node is a TEXT node."),
ok(!document.contains(focusNode), "LTR Focus node is an anonymous TEXT node."),
is(focusNode.parentNode, anchorNode, "LTR Focus node is a child of the Anchor DIV node."),
is(focusOffset, LTR_INPUT_TEXT_VALUE.length,
"LTR Selection ends at Focus node with offset of the LTRInput node length."),
is(anchorPt.y, focusPt.y,
"LTR UI Selection anchor should match focus vertically."),
lessThan(anchorPt.x, focusPt.x,
"LTR UI Selection anchor should be to the left of focus."),
]).then(function() {
// Close selection and complete test.
sh.observe(null, "TextSelection:End", {});
return Promise.all([
ok(!sh.isSelectionActive(),
"testLTR_selectAll finishes, selection should not be active."),
]);
});
}
/* =================================================================================
*
* RTL selectAll() test selects the entire single-line <input> element and ensures:
* ) The Selection exists.
* ) The Selection text matches an expected value.
*
* ) Assumptions about the DOM Selection Anchor node are correct.
* ) Assumptions about the DOM Selection Focus node are correct.
*
* ) The UI Selection anchor handle is aligned vertically with the focus handle.
* ) The UI Selection anchor handle is right of the focus handle.
*/
function testRTL_selectAll() {
// Select entire RTL Input element.
var sh = getSelectionHandler();
var element = document.getElementById("RTLInput");
element.value = RTL_INPUT_TEXT_VALUE;
sh.startSelection(element);
var selection = sh._getSelection();
var anchorNode = selection.anchorNode;
var anchorOffset = selection.anchorOffset;
var focusNode = selection.focusNode;
var focusOffset = selection.focusOffset;
var anchorPt = new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y);
var focusPt = new Point(sh._cache.focusPt.x, sh._cache.focusPt.y);
return Promise.all([
ok(sh.isSelectionActive(),
"testRTL_selectAll starts, selection should be active."),
is(sh._targetElement, element,
"RTL SelectionHandler reference is the node we provided."),
is(sh._getSelectedText(), RTL_INPUT_TEXT_VALUE,
"RTL Selection text should match expected value."),
isNot(anchorNode, element,
"RTL Selection Anchor isn't the RTLInput node."),
is(anchorNode.nodeName, DIV_NODE, "RTL Anchor node is a DIV node."),
ok(!document.contains(anchorNode), "RTL Anchor node is an anonymous DIV node."),
is(anchorNode.parentNode, element, "RTL Anchor node is a child of the RTLInput node."),
is(anchorOffset, 0,
"RTL Selection starts at Anchor node with offset 0."),
isNot(focusNode, element,
"RTL Selection Focus isn't the RTLInput node."),
is(focusNode.nodeName, TEXT_NODE, "RTL Focus node is a TEXT node."),
ok(!document.contains(focusNode), "RTL Focus node is an anonymous TEXT node."),
is(focusNode.parentNode, anchorNode, "RTL Focus node is a child of the Anchor DIV node."),
is(focusOffset, RTL_INPUT_TEXT_VALUE.length,
"RTL Selection ends at Focus node with offset of the RTLInput node length."),
is(anchorPt.y, focusPt.y,
"RTL UI Selection anchor should match focus vertically."),
greaterThan(anchorPt.x, focusPt.x,
"RTL UI Selection anchor should be to the right of focus."),
]).then(function() {
// Close selection and complete test.
sh.observe(null, "TextSelection:End", {});
return Promise.all([
ok(!sh.isSelectionActive(),
"testRTL_selectAll finishes, selection should not be active."),
]);
});
}
/* =================================================================================
*
* If we selectAll() in a LTR <input>, then:
* ) drag the focus handle to itself, the selected text, and the
* selection anchor and focus points should all remain the same.
*/
function testLTR_dragFocusHandleToSelf() {
// Select entire LTR Input element.
var sh = getSelectionHandler();
var element = document.getElementById("LTRInput");
element.value = LTR_INPUT_TEXT_VALUE;
sh.startSelection(element);
// Note initial Selection handle points.
var initialSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var initialSelectionText = sh._getSelectedText();
// Drag focus handle and note results.
sh.observe(null, "TextSelection:Move",
JSON.stringify({ handleType : FOCUS,
x : initialSelection.focusPt.x,
y : initialSelection.focusPt.y
})
);
sh.observe(null, "TextSelection:Position",
JSON.stringify({ handleType : FOCUS })
);
var focusDraggedSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var focusDragSelectionText = sh._getSelectedText();
// Complete test, and report.
sh.observe(null, "TextSelection:End", {});
return Promise.all([
ok(true, "testLTR_dragFocusHandleToSelf - Test Starts."),
is(initialSelectionText, LTR_INPUT_TEXT_VALUE,
"LTR Selection text initially should match expected value."),
selectionExists(initialSelection,
"LTR Selection initially existed at points"),
is(focusDragSelectionText, LTR_INPUT_TEXT_VALUE,
"LTR Selection text after focus drag should match expected value."),
selectionExists(focusDraggedSelection,
"LTR Selection after focus drag existed at points"),
selectionEquals(focusDraggedSelection, initialSelection,
"LTR Selection points after focus drag " +
"should match initial selection points."),
ok(true, "testLTR_dragFocusHandleToSelf - Test Finishes."),
]);
}
/* =================================================================================
*
* If we selectAll() in a LTR <input>, then:
* ) drag the anchor handle to itself, the selected text, and the
* selection anchor and focus points should all remain the same.
*/
function testLTR_dragAnchorHandleToSelf() {
// Select entire LTR Input element.
var sh = getSelectionHandler();
var element = document.getElementById("LTRInput");
element.value = LTR_INPUT_TEXT_VALUE;
sh.startSelection(element);
// Note initial Selection handle points.
var initialSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var initialSelectionText = sh._getSelectedText();
// Drag anchor handle and note results.
sh.observe(null, "TextSelection:Move",
JSON.stringify({ handleType : ANCHOR,
x : initialSelection.anchorPt.x,
y : initialSelection.anchorPt.y
})
);
sh.observe(null, "TextSelection:Position",
JSON.stringify({ handleType : ANCHOR })
);
var anchorDraggedSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var anchorDragSelectionText = sh._getSelectedText();
// Complete test, and report.
sh.observe(null, "TextSelection:End", {});
return Promise.all([
ok(true, "testLTR_dragAnchorHandleToSelf - Test Starts."),
is(initialSelectionText, LTR_INPUT_TEXT_VALUE,
"LTR Selection text initially should match expected value."),
selectionExists(initialSelection,
"LTR Selection initially existed at points"),
todo(false, "testLTR_dragAnchorHandleToSelf: " +
// is(anchorDragSelectionText, LTR_INPUT_TEXT_VALUE,
"LTR Selection text after anchor drag should match expected value."),
todo(false, "testLTR_dragAnchorHandleToSelf: " +
// selectionExists(anchorDraggedSelection,
"LTR Selection after anchor drag existed at points"),
todo(false, "testLTR_dragAnchorHandleToSelf: " +
// selectionEquals(anchorDraggedSelection, initialSelection,
"LTR Selection points after anchor drag " +
"should match initial selection points."),
ok(true, "testLTR_dragAnchorHandleToSelf - Test Finishes."),
]);
}
/* =================================================================================
*
* If we selectAll() in a RTL <input>, then:
* ) drag the focus handle to itself, the selected text, and the
* selection anchor and focus points should all remain the same.
*/
function testRTL_dragFocusHandleToSelf() {
// Select entire RTL Input element.
var sh = getSelectionHandler();
var element = document.getElementById("RTLInput");
element.value = RTL_INPUT_TEXT_VALUE;
sh.startSelection(element);
// Note initial Selection handle points.
var initialSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var initialSelectionText = sh._getSelectedText();
// Drag focus handle and note results.
sh.observe(null, "TextSelection:Move",
JSON.stringify({ handleType : FOCUS,
x : initialSelection.focusPt.x,
y : initialSelection.focusPt.y
})
);
sh.observe(null, "TextSelection:Position",
JSON.stringify({ handleType : FOCUS })
);
var focusDraggedSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var focusDragSelectionText = sh._getSelectedText();
// Complete test, and report.
sh.observe(null, "TextSelection:End", {});
return Promise.all([
ok(true, "testRTL_dragFocusHandleToSelf - Test Starts."),
is(initialSelectionText, RTL_INPUT_TEXT_VALUE,
"RTL Selection text initially should match expected value."),
selectionExists(initialSelection,
"RTL Selection initially existed at points"),
todo(false, "testRTL_dragAnchorHandleToSelf: " +
// is(focusDragSelectionText, RTL_INPUT_TEXT_VALUE,
"RTL Selection text after focus drag should match expected value."),
todo(false, "testRTL_dragAnchorHandleToSelf: " +
// selectionExists(focusDraggedSelection,
"RTL Selection after focus drag existed at points"),
todo(false, "testRTL_dragAnchorHandleToSelf: " +
// selectionEquals(focusDraggedSelection, initialSelection,
"RTL Selection points after focus drag " +
"should match initial selection points."),
ok(true, "testRTL_dragFocusHandleToSelf - Test Finishes."),
]);
}
/* =================================================================================
*
* If we selectAll() in a RTL <input>, then:
* ) drag the anchor handle to itself, the selected text, and the
* selection anchor and focus points should all remain the same.
*/
function testRTL_dragAnchorHandleToSelf() {
// Select entire RTL Input element.
var sh = getSelectionHandler();
var element = document.getElementById("RTLInput");
element.value = RTL_INPUT_TEXT_VALUE;
sh.startSelection(element);
// Note initial Selection handle points.
var initialSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var initialSelectionText = sh._getSelectedText();
// Drag anchor handle and note results.
sh.observe(null, "TextSelection:Move",
JSON.stringify({ handleType : ANCHOR,
x : initialSelection.anchorPt.x,
y : initialSelection.anchorPt.y
})
);
sh.observe(null, "TextSelection:Position",
JSON.stringify({ handleType : ANCHOR })
);
var anchorDraggedSelection =
{ anchorPt : new Point(sh._cache.anchorPt.x, sh._cache.anchorPt.y),
focusPt : new Point(sh._cache.focusPt.x, sh._cache.focusPt.y) };
var anchorDragSelectionText = sh._getSelectedText();
// Complete test, and report.
sh.observe(null, "TextSelection:End", {});
return Promise.all([
ok(true, "testRTL_dragAnchorHandleToSelf - Test Starts."),
is(initialSelectionText, RTL_INPUT_TEXT_VALUE,
"RTL Selection text initially should match expected value."),
selectionExists(initialSelection,
"RTL Selection initially existed at points"),
is(anchorDragSelectionText, RTL_INPUT_TEXT_VALUE,
"RTL Selection text after anchor drag should match expected value."),
selectionExists(anchorDraggedSelection,
"RTL Selection after anchor drag existed at points"),
selectionEquals(anchorDraggedSelection, initialSelection,
"RTL Selection points after anchor drag " +
"should match initial selection points."),
ok(true, "testRTL_dragAnchorHandleToSelf - Test Finishes."),
]);
}
/* =================================================================================
*
* After finish of all selection tests, wrap up and go home.
*
*/
function finishTests() {
Messaging.sendRequest({
type: "Robocop:testInputSelections",
result: true,
msg: "Done!",
done: true
});
}
/* ============================== Utility functions ======================
*
* Common functions available to all tests.
*
*/
function getSelectionHandler() {
return (!this._selectionHandler) ?
this._selectionHandler = Services.wm.getMostRecentWindow("navigator:browser").SelectionHandler :
this._selectionHandler;
}
function todo(result, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
todo: result,
msg: msg
});
}
function ok(result, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: result,
msg: msg
});
}
function is(one, two, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: one === two,
msg: msg + " : " + one + " === " + two
});
}
function isNot(one, two, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: one !== two,
msg: msg + " : " + one + " !== " + two
});
}
function lessThan(n1, n2, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: n1 < n2,
msg: msg + " : " + n1 + " < " + n2
});
}
function greaterThan(n1, n2, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: n1 > n2,
msg: msg + " : " + n1 + " > " + n2
});
}
function pointEquals(p1, p2, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: p1.equals(p2),
msg: msg + " : " + p1.toString() + " == " + p2.toString()
});
}
function pointNotEquals(p1, p2, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: !p1.equals(p2),
msg: msg + " : " + p1.toString() + " == " + p2.toString()
});
}
function selectionExists(selection, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: !selection.anchorPt.equals(selection.focusPt),
msg: msg + " : anchor:" + selection.anchorPt.toString() +
" focus:" + selection.focusPt.toString()
});
}
function selectionEquals(s1, s2, msg) {
return Messaging.sendRequestForResult({
type: "Robocop:testInputSelections",
result: s1.anchorPt.equals(s2.anchorPt) && s1.focusPt.equals(s2.focusPt),
msg: msg
});
}
/* =================================================================================
*
* Page definition for all tests.
*
*/
</script>
</head>
<body onload="startTests();">
<input id="LTRInput" dir="ltr" type="text" maxlength="57" size="57" value="">
<br>
<input id="RTLInput" dir="rtl" type="text" maxlength="35" size="35" value="">
</body>
</html>

View File

@ -0,0 +1,50 @@
package org.mozilla.gecko.tests;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.EventDispatcher;
import org.mozilla.gecko.tests.helpers.GeckoHelper;
import org.mozilla.gecko.tests.helpers.NavigationHelper;
import android.util.Log;
import org.json.JSONObject;
public class testInputSelections extends UITest {
public void testInputSelections() {
GeckoHelper.blockForReady();
Actions.EventExpecter robocopTestExpecter =
getActions().expectGeckoEvent("Robocop:testInputSelections");
final String url = "chrome://roboextender/content/testInputSelections.html";
NavigationHelper.enterAndLoadUrl(url);
mToolbar.assertTitle(url);
while (!test(robocopTestExpecter)) {
// do nothing
}
robocopTestExpecter.unregisterListener();
}
private boolean test(Actions.EventExpecter expecter) {
final JSONObject eventData;
try {
eventData = new JSONObject(expecter.blockForEventData());
} catch(Exception ex) {
// Log and ignore
getAsserter().ok(false, "JS Test", "Error decoding data " + ex);
return false;
}
if (eventData.has("result")) {
getAsserter().ok(eventData.optBoolean("result"), "JS Test", eventData.optString("msg"));
} else if (eventData.has("todo")) {
getAsserter().todo(eventData.optBoolean("todo"), "JS TODO", eventData.optString("msg"));
}
EventDispatcher.sendResponse(eventData, new JSONObject());
return eventData.optBoolean("done", false);
}
}

View File

@ -0,0 +1,62 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.gecko.util;
import org.mozilla.gecko.AppConstants.Versions;
import android.content.Context;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import java.lang.reflect.Method;
public class WindowUtils {
private static final String LOGTAG = "Gecko" + WindowUtils.class.getSimpleName();
private WindowUtils() { /* To prevent instantiation */ }
/**
* Returns the best-guess physical device dimensions, including the system status bars. Note
* that DisplayMetrics.height/widthPixels does not include the system bars.
*
* via http://stackoverflow.com/a/23861333
*
* @param context the calling Activity's Context
* @return The number of pixels of the device's largest dimension, ignoring software status bars
*/
public static int getLargestDimension(final Context context) {
final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
if (Versions.feature17Plus) {
final DisplayMetrics realMetrics = new DisplayMetrics();
display.getRealMetrics(realMetrics);
return Math.max(realMetrics.widthPixels, realMetrics.heightPixels);
} else if (Versions.feature14Plus) {
int tempWidth;
int tempHeight;
try {
final Method getRawH = Display.class.getMethod("getRawHeight");
final Method getRawW = Display.class.getMethod("getRawWidth");
tempWidth = (Integer) getRawW.invoke(display);
tempHeight = (Integer) getRawH.invoke(display);
} catch (Exception e) {
// This is the best we can do.
tempWidth = display.getWidth();
tempHeight = display.getHeight();
Log.w(LOGTAG, "Couldn't use reflection to get the real display metrics.");
}
return Math.max(tempWidth, tempHeight);
} else {
// This should be close, as lower API devices should not have window navigation bars.
return Math.max(display.getWidth(), display.getHeight());
}
}
}

View File

@ -233,6 +233,9 @@ struct RtspConnectionHandler : public AHandler {
}
void pause() {
if (!mSeekable) {
return;
}
AString request = "PAUSE ";
request.append(mSessionURL);
request.append(" RTSP/1.0\r\n");

View File

@ -51,8 +51,9 @@ RTSPSource::RTSPSource(
mDisconnectReplyID(0),
mLatestPausedUnit(0),
mPlayPending(false),
mSeekGeneration(0)
mSeekGeneration(0),
mDisconnectedToPauseLiveStream(false),
mPlayOnConnected(false)
{
CHECK(aListener != NULL);
@ -71,6 +72,8 @@ RTSPSource::~RTSPSource()
void RTSPSource::start()
{
mDisconnectedToPauseLiveStream = false;
if (mLooper == NULL) {
mLooper = new ALooper;
mLooper->setName("rtsp");
@ -95,6 +98,9 @@ void RTSPSource::start()
void RTSPSource::stop()
{
if (mState == DISCONNECTED) {
return;
}
sp<AMessage> msg = new AMessage(kWhatDisconnect, mReflector->id());
sp<AMessage> dummy;
@ -113,6 +119,14 @@ void RTSPSource::play()
void RTSPSource::pause()
{
LOGI("RTSPSource::pause()");
// Live streams can't be paused, so we have to disconnect now.
if (isLiveStream()) {
mDisconnectedToPauseLiveStream = true;
stop();
return;
}
sp<AMessage> msg = new AMessage(kWhatPerformPause, mReflector->id());
msg->post();
}
@ -212,6 +226,7 @@ status_t RTSPSource::seekTo(int64_t seekTimeUs) {
void RTSPSource::performPlay(int64_t playTimeUs) {
if (mState == DISCONNECTED) {
LOGI("We are in a idle state, restart play");
mPlayOnConnected = true;
start();
return;
}
@ -641,6 +656,11 @@ void RTSPSource::onConnected(bool isSeekable)
}
mState = CONNECTED;
if (mPlayOnConnected) {
mPlayOnConnected = false;
play();
}
}
void RTSPSource::onDisconnected(const sp<AMessage> &msg) {
@ -659,7 +679,9 @@ void RTSPSource::onDisconnected(const sp<AMessage> &msg) {
if (mDisconnectReplyID != 0) {
finishDisconnectIfPossible();
}
if (mListener) {
// If the disconnection is caused by pausing live stream,
// do not report back to the controller.
if (mListener && !mDisconnectedToPauseLiveStream) {
nsresult reason = (err == OK) ? NS_OK : NS_ERROR_NET_TIMEOUT;
mListener->OnDisconnected(0, reason);
// Break the cycle reference between RtspController and us.
@ -728,7 +750,9 @@ void RTSPSource::onTrackDataAvailable(size_t trackIndex)
void RTSPSource::onTrackEndOfStream(size_t trackIndex)
{
if (!mListener) {
// If we are disconnecting to pretend pausing a live stream,
// do not report the end of stream.
if (!mListener || mDisconnectedToPauseLiveStream) {
return;
}
@ -741,4 +765,11 @@ void RTSPSource::onTrackEndOfStream(size_t trackIndex)
mListener->OnMediaDataAvailable(trackIndex, data, data.Length(), 0, meta.get());
}
bool RTSPSource::isLiveStream() {
int64_t duration = 0;
getDuration(&duration);
return duration == 0;
}
} // namespace android

View File

@ -152,6 +152,18 @@ private:
void onTrackEndOfStream(size_t trackIndex);
bool isLiveStream();
// This flag is set if we have just disconnected
// in order to pretend pausing a live stream.
bool mDisconnectedToPauseLiveStream;
// While performing a play operation, if the current state of RTSP connection
// is disconnected, we will start over establishing connection to the server.
// In this case (mPlayOnConnected = true), we have to perform play again when
// onConnected, to ensure we complete the play operation.
bool mPlayOnConnected;
nsMainThreadPtrHandle<nsIStreamingProtocolListener> mListener;
int mPrintCount;

View File

@ -159,11 +159,11 @@ let AnimationPlayerActor = ActorClass({
* @return {Object}
*/
getCurrentState: method(function() {
return {
/**
* Return the player's current startTime value.
* Will be null whenever the animation is paused or waiting to start.
*/
// Note that if you add a new property to the state object, make sure you
// add the corresponding property in the AnimationPlayerFront' initialState
// getter.
let newState = {
// startTime is null whenever the animation is paused or waiting to start.
startTime: this.player.startTime,
currentTime: this.player.currentTime,
playState: this.player.playState,
@ -171,16 +171,32 @@ let AnimationPlayerActor = ActorClass({
duration: this.getDuration(),
delay: this.getDelay(),
iterationCount: this.getIterationCount(),
/**
* Is the animation currently running on the compositor. This is important for
* developers to know if their animation is hitting the fast path or not.
* Currently this will only be true for Firefox OS though (where we have
* compositor animations enabled).
* Returns false whenever the animation is paused as it is taken off the
* compositor then.
*/
// isRunningOnCompositor is important for developers to know if their
// animation is hitting the fast path or not. Currently only true for
// Firefox OS (where we have compositor animations enabled).
// Returns false whenever the animation is paused as it is taken off the
// compositor then.
isRunningOnCompositor: this.player.isRunningOnCompositor
};
// If we've saved a state before, compare and only send what has changed.
// It's expected of the front to also save old states to re-construct the
// full state when an incomplete one is received.
// This is to minimize protocol traffic.
let sentState = {};
if (this.currentState) {
for (let key in newState) {
if (typeof this.currentState[key] === "undefined" ||
this.currentState[key] !== newState[key]) {
sentState[key] = newState[key];
}
}
} else {
sentState = newState;
}
this.currentState = newState;
return sentState;
}, {
request: {},
response: {
@ -323,20 +339,36 @@ let AnimationPlayerFront = FrontClass(AnimationPlayerActor, {
return;
}
// Check if something has changed
let hasChanged = false;
for (let key in data) {
if (this.state[key] !== data[key]) {
hasChanged = true;
break;
}
// If the animationplayer is now finished, stop auto-refreshing.
if (data.playState === "finished") {
this.stopAutoRefresh();
}
if (hasChanged) {
if (this.currentStateHasChanged) {
this.state = data;
this.emit(this.AUTO_REFRESH_EVENT, this.state);
}
})
}),
/**
* getCurrentState interceptor re-constructs incomplete states since the actor
* only sends the values that have changed.
*/
getCurrentState: protocol.custom(function() {
this.currentStateHasChanged = false;
return this._getCurrentState().then(data => {
for (let key in this.state) {
if (typeof data[key] === "undefined") {
data[key] = this.state[key];
} else if (data[key] !== this.state[key]) {
this.currentStateHasChanged = true;
}
}
return data;
});
}, {
impl: "_getCurrentState"
}),
});
/**

View File

@ -0,0 +1,179 @@
/* 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/. */
const {Cc, Ci, Cu, CC} = require("chrome");
const protocol = require("devtools/server/protocol");
const {Arg, method, RetVal} = protocol;
const {DebuggerServer} = require("devtools/server/main");
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let defaultSettings = {};
let settingsFile;
exports.register = function(handle) {
handle.addGlobalActor(SettingsActor, "settingsActor");
};
exports.unregister = function(handle) {
};
function getDefaultSettings() {
let chan = NetUtil.newChannel(settingsFile);
let stream = chan.open();
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString(
stream,
stream.available()) || "");
try {
defaultSettings = JSON.parse(rawstr);
} catch(e) { }
stream.close();
}
function loadSettingsFile() {
// Loading resource://app/defaults/settings.json doesn't work because
// settings.json is not in the omnijar.
// So we look for the app dir instead and go from here...
if (settingsFile) {
return;
}
settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false);
if (!settingsFile || (settingsFile && !settingsFile.exists())) {
// On b2g desktop builds the settings.json file is moved in the
// profile directory by the build system.
settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false);
if (!settingsFile || (settingsFile && !settingsFile.exists())) {
console.log("settings.json file does not exist");
}
}
if (settingsFile.exists()) {
getDefaultSettings();
}
}
let SettingsActor = exports.SettingsActor = protocol.ActorClass({
typeName: "settings",
_getSettingsService: function() {
let win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
return win.navigator.mozSettings;
},
getSetting: method(function(name) {
let deferred = promise.defer();
let lock = this._getSettingsService().createLock();
let req = lock.get(name);
req.onsuccess = function() {
deferred.resolve(req.result[name]);
};
req.onerror = function() {
deferred.reject(req.error);
};
return deferred.promise;
}, {
request: { value: Arg(0) },
response: { value: RetVal("json") }
}),
setSetting: method(function(name, value) {
let deferred = promise.defer();
let data = {};
data[name] = value;
let lock = this._getSettingsService().createLock();
let req = lock.set(data);
req.onsuccess = function() {
deferred.resolve(true);
};
req.onerror = function() {
deferred.reject(req.error);
};
return deferred.promise;
}, {
request: { name: Arg(0), value: Arg(1) },
response: {}
}),
_hasUserSetting: function(name, value) {
if (typeof value === "object") {
return JSON.stringify(defaultSettings[name]) !== JSON.stringify(value);
}
return (defaultSettings[name] !== value);
},
getAllSettings: method(function() {
loadSettingsFile();
let settings = {};
let self = this;
let deferred = promise.defer();
let lock = this._getSettingsService().createLock();
let req = lock.get("*");
req.onsuccess = function() {
for (var name in req.result) {
settings[name] = {
value: req.result[name],
hasUserValue: self._hasUserSetting(name, req.result[name])
};
}
deferred.resolve(settings);
};
req.onfailure = function() {
deferred.reject(req.error);
};
return deferred.promise;
}, {
request: {},
response: { value: RetVal("json") }
}),
clearUserSetting: method(function(name) {
loadSettingsFile();
try {
this.setSetting(name, defaultSettings[name]);
} catch (e) {
console.log(e);
}
}, {
request: { name: Arg(0) },
response: {}
})
});
let SettingsFront = protocol.FrontClass(SettingsActor, {
initialize: function(client, form) {
protocol.Front.prototype.initialize.call(this, client);
this.actorID = form.settingsActor;
this.manage(this);
},
});
const _knownSettingsFronts = new WeakMap();
exports.getSettingsFront = function(client, form) {
if (!form.settingsActor) {
return null;
}
if (_knownSettingsFronts.has(client)) {
return _knownSettingsFronts.get(client);
}
let front = new SettingsFront(client, form);
_knownSettingsFronts.set(client, front);
return front;
};
// For tests
exports._setDefaultSettings = function(settings) {
defaultSettings = settings || {};
};

View File

@ -376,7 +376,14 @@ var DebuggerServer = {
type: { global: true }
});
}
let win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
if (win && win.navigator.mozSettings) {
this.registerModule("devtools/server/actors/settings", {
prefix: "settings",
constructor: "SettingsActor",
type: { global: true }
});
}
this.registerModule("devtools/server/actors/webapps", {
prefix: "webapps",
constructor: "WebappsActor",

View File

@ -55,6 +55,7 @@ EXTRA_JS_MODULES.devtools.server.actors += [
'actors/profiler.js',
'actors/root.js',
'actors/script.js',
'actors/settings.js',
'actors/storage.js',
'actors/string.js',
'actors/styleeditor.js',

View File

@ -20,6 +20,7 @@ support-files =
[browser_animation_actors_04.js]
[browser_animation_actors_05.js]
[browser_animation_actors_06.js]
[browser_animation_actors_07.js]
[browser_navigateEvents.js]
[browser_storage_dynamic_windows.js]
[browser_storage_listings.js]

View File

@ -0,0 +1,46 @@
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Check that, even though the AnimationPlayerActor only sends the bits of its
// state that change, the front reconstructs the whole state everytime.
const {AnimationsFront} = require("devtools/server/actors/animation");
const {InspectorFront} = require("devtools/server/actors/inspector");
add_task(function*() {
let doc = yield addTab(MAIN_DOMAIN + "animation.html");
initDebuggerServer();
let client = new DebuggerClient(DebuggerServer.connectPipe());
let form = yield connectDebuggerClient(client);
let inspector = InspectorFront(client, form);
let walker = yield inspector.getWalker();
let front = AnimationsFront(client, form);
yield playerHasCompleteStateAtAllTimes(walker, front);
yield closeDebuggerClient(client);
gBrowser.removeCurrentTab();
});
function* playerHasCompleteStateAtAllTimes(walker, front) {
let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
let [player] = yield front.getAnimationPlayersForNode(node);
yield player.ready();
// Get the list of state key names from the initialstate.
let keys = Object.keys(player.initialState);
// Get the state over and over again and check that the object returned
// contains all keys.
// Normally, only the currentTime will have changed in between 2 calls.
for (let i = 0; i < 10; i ++) {
let state = yield player.getCurrentState();
keys.forEach(key => {
ok(typeof state[key] !== "undefined", "The state retrieved has key " + key);
});
}
}

View File

@ -71,6 +71,7 @@ skip-if = buildapp == 'mulet'
[test_memory_census.html]
[test_memory_gc_01.html]
[test_preference.html]
[test_settings.html]
[test_connectToChild.html]
skip-if = buildapp == 'mulet'
[test_attachProcess.html]

View File

@ -0,0 +1,129 @@
<!DOCTYPE HTML>
<html>
<!--
Bug 1022797 - Settings support from WebIDE
-->
<head>
<meta charset="utf-8">
<title>Test Settings Actor</title>
<script type="text/javascript" src="chrome://mochikit/content/MochiKit/MochiKit.js"></script>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script>
function runTests() {
var Cu = Components.utils;
var Cc = Components.classes;
var Ci = Components.interfaces;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
if (SpecialPowers.isMainProcess()) {
Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
}
SimpleTest.waitForExplicitFinish();
var {getSettingsFront, _setDefaultSettings} = devtools.require("devtools/server/actors/settings");
DebuggerServer.init(function () { return true; });
DebuggerServer.addBrowserActors();
var client = new DebuggerClient(DebuggerServer.connectPipe());
client.connect(function onConnect() {
client.listTabs(function onListTabs(aResponse) {
var s = getSettingsFront(client, aResponse);
var settings = {};
var resetSettings = {};
var fakeSettings = {
"wifi.enabled": true,
"audio.volume.alarm": 15,
"app.reportCrashes": "ask",
"app.someObject": { active: true }
};
var localSetting = {
"wifi.enabled": false,
"audio.volume.alarm": 0,
"app.reportCrashes": "none",
"app.someObject": {}
};
function checkValues() {
is(settings.allSettings["wifi.enabled"].hasUserValue, false, "original unchanged bool setting");
is(settings.allSettings["audio.volume.alarm"].hasUserValue, false, "original unchanged int setting");
is(settings.allSettings["app.reportCrashes"].hasUserValue, false, "original unchanged string setting");
is(settings.allSettings["app.someObject"].hasUserValue, false, "original unchanged object setting");
is(settings.allSettings["wifi.enabled"].value, fakeSettings["wifi.enabled"], "original read/write bool setting");
is(settings.allSettings["audio.volume.alarm"].value, fakeSettings["audio.volume.alarm"], "original read/write int setting");
is(settings.allSettings["app.reportCrashes"].value, fakeSettings["app.reportCrashes"], "original read/write string setting");
is(JSON.stringify(settings.allSettings["app.someObject"].value), JSON.stringify(fakeSettings["app.someObject"]), "original read/write object setting");
is(settings.allUpdatedSettings["wifi.enabled"].hasUserValue, true, "updated user-changed bool setting");
is(settings.allUpdatedSettings["audio.volume.alarm"].hasUserValue, true, "updated user-changed int setting");
is(settings.allUpdatedSettings["app.reportCrashes"].hasUserValue, true, "updated user-changed string setting");
is(settings.allUpdatedSettings["app.someObject"].hasUserValue, true, "updated user-changed object setting");
is(settings["wifi.enabled"], localSetting["wifi.enabled"], "updated bool setting");
is(settings["audio.volume.alarm"], localSetting["audio.volume.alarm"], "updated int setting");
is(settings["app.reportCrashes"], localSetting["app.reportCrashes"], "updated string setting");
is(JSON.stringify(settings["app.someObject"]), JSON.stringify(localSetting["app.someObject"]), "updated object as string setting");
is(resetSettings["wifi.enabled"], fakeSettings["wifi.enabled"], "reset to original bool setting");
is(resetSettings["audio.volume.alarm"], fakeSettings["audio.volume.alarm"], "reset to original int setting");
is(resetSettings["app.reportCrashes"], fakeSettings["app.reportCrashes"], "reset to original string setting");
is(JSON.stringify(resetSettings["app.someObject"]), JSON.stringify(fakeSettings["app.someObject"]), "reset to original object setting");
client.close(() => {
DebuggerServer.destroy();
SimpleTest.finish();
});
}
// settings.json doesn't exist outside of b2g so we will fake it.
_setDefaultSettings(fakeSettings);
s.setSetting("wifi.enabled", fakeSettings["wifi.enabled"])
.then(() => s.setSetting("audio.volume.alarm", fakeSettings["audio.volume.alarm"]))
.then(() => s.setSetting("app.reportCrashes", fakeSettings["app.reportCrashes"]))
.then(() => s.setSetting("app.someObject", fakeSettings["app.someObject"]))
.then(() => s.getAllSettings().then(json => settings.allSettings = json))
.then(() => s.setSetting("wifi.enabled", localSetting["wifi.enabled"]))
.then(() => s.setSetting("audio.volume.alarm", localSetting["audio.volume.alarm"]))
.then(() => s.setSetting("app.reportCrashes", localSetting["app.reportCrashes"]))
.then(() => s.setSetting("app.someObject", localSetting["app.someObject"]))
.then(() => s.getAllSettings().then(json => settings.allUpdatedSettings = json))
.then(() => s.getSetting("wifi.enabled")).then(value => settings["wifi.enabled"] = value)
.then(() => s.getSetting("audio.volume.alarm")).then(value => settings["audio.volume.alarm"] = value)
.then(() => s.getSetting("app.reportCrashes")).then(value => settings["app.reportCrashes"] = value)
.then(() => s.getSetting("app.someObject")).then(value => settings["app.someObject"] = value)
.then(() => s.clearUserSetting("wifi.enabled")).then(() => {
s.getSetting("wifi.enabled").then(value => resetSettings["wifi.enabled"] = value);
})
.then(() => s.clearUserSetting("audio.volume.alarm")).then(() => {
s.getSetting("audio.volume.alarm").then(value => resetSettings["audio.volume.alarm"] = value);
})
.then(() => s.clearUserSetting("app.reportCrashes")).then(() => {
s.getSetting("app.reportCrashes").then(value => resetSettings["app.reportCrashes"] = value);
})
.then(() => s.clearUserSetting("app.someObject")).then(() => {
s.getSetting("app.someObject").then(value => {
resetSettings["app.someObject"] = value
}).then(checkValues);
});
});
});
}
window.onload = function () {
runTests();
}
</script>
</pre>
</body>
</html>