Merge m-c to inbound a=merge

This commit is contained in:
Wes Kocher 2014-09-19 17:38:26 -07:00
commit 7904e1ccbc
52 changed files with 994 additions and 404 deletions

View File

@ -8,16 +8,13 @@ module.metadata = {
"stability": "stable"
};
const { Cc, Ci, Cu } = require("chrome");
const { Cc, Ci } = require("chrome");
const file = require("./io/file");
const prefs = require("./preferences/service");
const jpSelf = require("./self");
const timer = require("./timers");
const unload = require("./system/unload");
const { emit, on, off } = require("./event/core");
const { defer } = require('./core/promise');
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
const WRITE_PERIOD_DEFAULT = 300000; // 5 minutes
@ -38,57 +35,6 @@ Object.defineProperties(exports, {
}
});
function getHash(data) {
let { promise, resolve } = defer();
let crypto = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
crypto.init(crypto.MD5);
let listener = {
onStartRequest: function() { },
onDataAvailable: function(request, context, inputStream, offset, count) {
crypto.updateFromStream(inputStream, count);
},
onStopRequest: function(request, context, status) {
resolve(crypto.finish(false));
}
};
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
let stream = converter.convertToInputStream(data);
let pump = Cc["@mozilla.org/network/input-stream-pump;1"].
createInstance(Ci.nsIInputStreamPump);
pump.init(stream, -1, -1, 0, 0, true);
pump.asyncRead(listener, null);
return promise;
}
function writeData(filename, data) {
let { promise, resolve, reject } = defer();
let stream = file.open(filename, "w");
try {
stream.writeAsync(data, err => {
if (err)
reject(err);
else
resolve();
});
}
catch (err) {
// writeAsync closes the stream after it's done, so only close on error.
stream.close();
reject(err);
}
return promise;
}
// A generic JSON store backed by a file on disk. This should be isolated
// enough to move to its own module if need be...
function JsonStore(options) {
@ -97,9 +43,11 @@ function JsonStore(options) {
this.writePeriod = options.writePeriod;
this.onOverQuota = options.onOverQuota;
this.onWrite = options.onWrite;
this.hash = null;
unload.ensure(this);
this.startTimer();
this.writeTimer = timer.setInterval(this.write.bind(this),
this.writePeriod);
}
JsonStore.prototype = {
@ -133,18 +81,11 @@ JsonStore.prototype = {
undefined;
},
startTimer: function JsonStore_startTimer() {
timer.setTimeout(() => {
this.write().then(this.startTimer.bind(this));
}, this.writePeriod);
},
// Removes the backing file and all empty subdirectories.
purge: function JsonStore_purge() {
try {
// This'll throw if the file doesn't exist.
file.remove(this.filename);
this.hash = null;
let parentPath = this.filename;
do {
parentPath = file.dirname(parentPath);
@ -164,25 +105,31 @@ JsonStore.prototype = {
// errors cause tests to fail. Supporting "known" errors in the test
// harness appears to be non-trivial. Maybe later.
this.root = JSON.parse(str);
let self = this;
getHash(str).then(hash => this.hash = hash);
}
catch (err) {
this.root = {};
this.hash = null;
}
},
// If the store is under quota, writes the root to the backing file.
// Otherwise quota observers are notified and nothing is written.
write: function JsonStore_write() {
if (this.quotaUsage > 1)
this.onOverQuota(this);
else
this._write();
},
// Cleans up on unload. If unloading because of uninstall, the store is
// purged; otherwise it's written.
unload: function JsonStore_unload(reason) {
timer.clearTimeout(this.writeTimer);
timer.clearInterval(this.writeTimer);
this.writeTimer = null;
if (reason === "uninstall")
this.purge();
else
this.write();
this._write();
},
// True if the root is an empty object.
@ -201,40 +148,32 @@ JsonStore.prototype = {
// Writes the root to the backing file, notifying write observers when
// complete. If the store is over quota or if it's empty and the store has
// never been written, nothing is written and write observers aren't notified.
write: Task.async(function JsonStore_write() {
_write: function JsonStore__write() {
// Don't write if the root is uninitialized or if the store is empty and the
// backing file doesn't yet exist.
if (!this.isRootInited || (this._isEmpty && !file.exists(this.filename)))
return;
let data = JSON.stringify(this.root);
// If the store is over quota, don't write. The current under-quota state
// should persist.
if ((this.quota > 0) && (data.length > this.quota)) {
this.onOverQuota(this);
return;
}
// Hash the data to compare it to any previously written data
let hash = yield getHash(data);
if (hash == this.hash)
if (this.quotaUsage > 1)
return;
// Finally, write.
let stream = file.open(this.filename, "w");
try {
yield writeData(this.filename, data);
this.hash = hash;
if (this.onWrite)
this.onWrite(this);
stream.writeAsync(JSON.stringify(this.root), function writeAsync(err) {
if (err)
console.error("Error writing simple storage file: " + this.filename);
else if (this.onWrite)
this.onWrite(this);
}.bind(this));
}
catch (err) {
console.error("Error writing simple storage file: " + this.filename);
console.error(err);
// writeAsync closes the stream after it's done, so only close on error.
stream.close();
}
})
}
};

View File

@ -6,7 +6,6 @@ const file = require("sdk/io/file");
const prefs = require("sdk/preferences/service");
const QUOTA_PREF = "extensions.addon-sdk.simple-storage.quota";
const WRITE_PERIOD_PREF = "extensions.addon-sdk.simple-storage.writePeriod";
let {Cc,Ci} = require("chrome");
@ -35,13 +34,12 @@ exports.testSetGet = function (assert, done) {
// Load the module again and make sure the value stuck.
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(ss.storage.foo, val, "Value should persist");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
file.remove(storeFilename);
done();
};
assert.equal(ss.storage.foo, val, "Value should persist");
loader.unload();
file.remove(storeFilename);
done();
};
let val = "foo";
ss.storage.foo = val;
@ -162,11 +160,10 @@ exports.testQuotaExceededHandle = function (assert, done) {
assert.equal(ss.storage.x, 4, "x value should be correct");
assert.equal(ss.storage.y, 5, "y value should be correct");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
prefs.reset(QUOTA_PREF);
done();
};
loader.unload();
prefs.reset(QUOTA_PREF);
done();
};
loader.unload();
});
@ -200,9 +197,6 @@ exports.testQuotaExceededNoHandle = function (assert, done) {
assert.equal(ss.storage, val,
"Over-quota value should not have been written, " +
"old value should have persisted: " + ss.storage);
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
prefs.reset(QUOTA_PREF);
done();
@ -257,184 +251,6 @@ exports.testUninstall = function (assert, done) {
loader.unload();
};
exports.testChangeInnerArray = function(assert, done) {
prefs.set(WRITE_PERIOD_PREF, 10);
let expected = {
x: [5, 7],
y: [7, 28],
z: [6, 2]
};
// Load the module, set a value.
let loader = Loader(module);
let ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function (storage) {
assert.ok(file.exists(storeFilename), "Store file should exist");
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Add a property
ss.storage.x.push(["bar"]);
expected.x.push(["bar"]);
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify a property
ss.storage.y[0] = 42;
expected.y[0] = 42;
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Delete a property
delete ss.storage.z[1];
delete expected.z[1];
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify the new inner-object
ss.storage.x[2][0] = "baz";
expected.x[2][0] = "baz";
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
file.remove(storeFilename);
prefs.reset(WRITE_PERIOD_PREF);
done();
};
};
};
};
};
ss.storage = {
x: [5, 7],
y: [7, 28],
z: [6, 2]
};
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
};
exports.testChangeInnerObject = function(assert, done) {
prefs.set(WRITE_PERIOD_PREF, 10);
let expected = {
x: {
a: 5,
b: 7
},
y: {
c: 7,
d: 28
},
z: {
e: 6,
f: 2
}
};
// Load the module, set a value.
let loader = Loader(module);
let ss = loader.require("sdk/simple-storage");
manager(loader).jsonStore.onWrite = function (storage) {
assert.ok(file.exists(storeFilename), "Store file should exist");
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Add a property
ss.storage.x.g = {foo: "bar"};
expected.x.g = {foo: "bar"};
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify a property
ss.storage.y.c = 42;
expected.y.c = 42;
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Delete a property
delete ss.storage.z.f;
delete expected.z.f;
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
// Modify the new inner-object
ss.storage.x.g.foo = "baz";
expected.x.g.foo = "baz";
manager(loader).jsonStore.onWrite = function (storage) {
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
};
loader.unload();
// Load the module again and check the result
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
file.remove(storeFilename);
prefs.reset(WRITE_PERIOD_PREF);
done();
};
};
};
};
};
ss.storage = {
x: {
a: 5,
b: 7
},
y: {
c: 7,
d: 28
},
z: {
e: 6,
f: 2
}
};
assert.equal(JSON.stringify(ss.storage),
JSON.stringify(expected), "Should see the expected object");
loader.unload();
};
exports.testSetNoSetRead = function (assert, done) {
// Load the module, set a value.
let loader = Loader(module);
@ -453,13 +269,12 @@ exports.testSetNoSetRead = function (assert, done) {
// Load the module a third time and make sure the value stuck.
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.equal(ss.storage.foo, val, "Value should persist");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
file.remove(storeFilename);
done();
};
assert.equal(ss.storage.foo, val, "Value should persist");
loader.unload();
file.remove(storeFilename);
done();
};
let val = "foo";
ss.storage.foo = val;
@ -480,13 +295,12 @@ function setGetRoot(assert, done, val, compare) {
// Load the module again and make sure the value stuck.
loader = Loader(module);
ss = loader.require("sdk/simple-storage");
assert.ok(compare(ss.storage, val), "Value should persist");
manager(loader).jsonStore.onWrite = function (storage) {
assert.fail("Nothing should be written since `storage` was not changed.");
manager(loader).jsonStore.onWrite = function () {
file.remove(storeFilename);
done();
};
assert.ok(compare(ss.storage, val), "Value should persist");
loader.unload();
file.remove(storeFilename);
done();
};
ss.storage = val;
assert.ok(compare(ss.storage, val), "Value read should be value set");

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>

View File

@ -19,8 +19,8 @@
<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="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f3e998242fb9a857cf50f5bf3a02304a530ea617"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>

View File

@ -19,8 +19,8 @@
<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="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c058843242068d0df7c107e09da31b53d2e08fa6"/>

View File

@ -15,9 +15,9 @@
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f3e998242fb9a857cf50f5bf3a02304a530ea617"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "02fabec9910191bf6f99cb9879a1e6603d5ed7c3",
"revision": "b5f6c10e258311d9f04ed759291bbe6af10a772b",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,8 +17,8 @@
<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="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>

View File

@ -15,8 +15,8 @@
<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="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>

View File

@ -17,8 +17,8 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="f3e998242fb9a857cf50f5bf3a02304a530ea617"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>

View File

@ -17,8 +17,8 @@
<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="12d634c63ec8a41553a9f150c38260c992dc0020"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f293787d4a86257c9e78a35bd3f73b31b706e2"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="c7ef0bf06ce1c98cbe68aa52e2ecd862acb23e9c"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="b81855b6b67f285d6f27a4f8c1cfe2e0387ea57c"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>

View File

@ -97,6 +97,15 @@ const injectObjectAPI = function(api, targetWindow) {
return contentObj;
};
/**
* Get the two-digit hexadecimal code for a byte
*
* @param {byte} charCode
*/
const toHexString = function(charCode) {
return ("0" + charCode.toString(16)).slice(-2);
};
/**
* Inject the loop API into the given window. The caller must be sure the
* window is a loop content window (eg, a panel, chatwindow, or similar).
@ -510,6 +519,42 @@ function injectLoopAPI(targetWindow) {
Services.telemetry.getHistogramById(histogramId).add(value);
}
},
/**
* Compose a URL pointing to the location of an avatar by email address.
* At the moment we use the Gravatar service to match email addresses with
* avatars. This might change in the future as avatars might come from another
* source.
*
* @param {String} emailAddress Users' email address
* @param {Number} size Size of the avatar image to return in pixels.
* Optional. Default value: 40.
* @return the URL pointing to an avatar matching the provided email address.
*/
getUserAvatar: {
enumerable: true,
writable: true,
value: function(emailAddress, size = 40) {
if (!emailAddress) {
return "";
}
// Do the MD5 dance.
let hasher = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
hasher.init(Ci.nsICryptoHash.MD5);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stringStream.data = emailAddress.trim().toLowerCase();
hasher.updateFromStream(stringStream, -1);
let hash = hasher.finish(false);
// Convert the binary hash data to a hex string.
let md5Email = [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
// Compose the Gravatar URL.
return "http://www.gravatar.com/avatar/" + md5Email + ".jpg?default=blank&s=" + size;
}
},
};
function onStatusChanged(aSubject, aTopic, aData) {

View File

@ -0,0 +1,195 @@
/** @jsx React.DOM */
/* 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/. */
/*jshint newcap:false*/
/*global loop:true, React */
var loop = loop || {};
loop.contacts = (function(_, mozL10n) {
"use strict";
// Number of contacts to add to the list at the same time.
const CONTACTS_CHUNK_SIZE = 100;
const ContactDetail = React.createClass({displayName: 'ContactDetail',
propTypes: {
handleContactClick: React.PropTypes.func,
contact: React.PropTypes.object.isRequired
},
handleContactClick: function() {
if (this.props.handleContactClick) {
this.props.handleContactClick(this.props.key);
}
},
getContactNames: function() {
// The model currently does not enforce a name to be present, but we're
// going to assume it is awaiting more advanced validation of required fields
// by the model. (See bug 1069918)
// NOTE: this method of finding a firstname and lastname is not i18n-proof.
let names = this.props.contact.name[0].split(" ");
return {
firstName: names.shift(),
lastName: names.join(" ")
};
},
getPreferredEmail: function() {
// The model currently does not enforce a name to be present, but we're
// going to assume it is awaiting more advanced validation of required fields
// by the model. (See bug 1069918)
let email = this.props.contact.email[0];
this.props.contact.email.some(function(address) {
if (address.pref) {
email = address;
return true;
}
return false;
});
return email;
},
render: function() {
let names = this.getContactNames();
let email = this.getPreferredEmail();
let cx = React.addons.classSet;
let contactCSSClass = cx({
contact: true,
blocked: this.props.contact.blocked
});
return (
React.DOM.li({onClick: this.handleContactClick, className: contactCSSClass},
React.DOM.div({className: "avatar"},
React.DOM.img({src: navigator.mozLoop.getUserAvatar(email.value)})
),
React.DOM.div({className: "details"},
React.DOM.div({className: "username"}, React.DOM.strong(null, names.firstName), " ", names.lastName,
React.DOM.i({className: cx({"icon icon-google": this.props.contact.category[0] == "google"})}),
React.DOM.i({className: cx({"icon icon-blocked": this.props.contact.blocked})})
),
React.DOM.div({className: "email"}, email.value)
),
React.DOM.div({className: "icons"},
React.DOM.i({className: "icon icon-video"}),
React.DOM.i({className: "icon icon-caret-down"})
)
)
);
}
});
const ContactsList = React.createClass({displayName: 'ContactsList',
getInitialState: function() {
return {
contacts: {}
};
},
componentDidMount: function() {
let contactsAPI = navigator.mozLoop.contacts;
contactsAPI.getAll((err, contacts) => {
if (err) {
throw err;
}
// Add contacts already present in the DB. We do this in timed chunks to
// circumvent blocking the main event loop.
let addContactsInChunks = () => {
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
this.handleContactAddOrUpdate(contact);
});
if (contacts.length) {
setTimeout(addContactsInChunks, 0);
}
};
addContactsInChunks(contacts);
// Listen for contact changes/ updates.
contactsAPI.on("add", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
contactsAPI.on("remove", (eventName, contact) => {
this.handleContactRemove(contact);
});
contactsAPI.on("removeAll", () => {
this.handleContactRemoveAll();
});
contactsAPI.on("update", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
});
},
handleContactAddOrUpdate: function(contact) {
let contacts = this.state.contacts;
let guid = String(contact._guid);
contacts[guid] = contact;
this.setState({});
},
handleContactRemove: function(contact) {
let contacts = this.state.contacts;
let guid = String(contact._guid);
if (!contacts[guid]) {
return;
}
delete contacts[guid];
this.setState({});
},
handleContactRemoveAll: function() {
this.setState({contacts: {}});
},
sortContacts: function(contact1, contact2) {
let comp = contact1.name[0].localeCompare(contact2.name[0]);
if (comp !== 0) {
return comp;
}
// If names are equal, compare against unique ids to make sure we have
// consistent ordering.
return contact1._guid - contact2._guid;
},
render: function() {
let viewForItem = item => {
return ContactDetail({key: item._guid, contact: item})
};
let shownContacts = _.groupBy(this.state.contacts, function(contact) {
return contact.blocked ? "blocked" : "available";
});
return (
React.DOM.div({className: "listWrapper"},
React.DOM.div({ref: "listSlider", className: "listPanels"},
React.DOM.div({className: "faded"},
React.DOM.ul(null,
shownContacts.available ?
shownContacts.available.sort(this.sortContacts).map(viewForItem) :
null,
shownContacts.blocked ?
React.DOM.h3({className: "header"}, mozL10n.get("contacts_blocked_contacts")) :
null,
shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null
)
)
)
)
);
}
});
return {
ContactsList: ContactsList
};
})(_, document.mozL10n);

View File

@ -0,0 +1,195 @@
/** @jsx React.DOM */
/* 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/. */
/*jshint newcap:false*/
/*global loop:true, React */
var loop = loop || {};
loop.contacts = (function(_, mozL10n) {
"use strict";
// Number of contacts to add to the list at the same time.
const CONTACTS_CHUNK_SIZE = 100;
const ContactDetail = React.createClass({
propTypes: {
handleContactClick: React.PropTypes.func,
contact: React.PropTypes.object.isRequired
},
handleContactClick: function() {
if (this.props.handleContactClick) {
this.props.handleContactClick(this.props.key);
}
},
getContactNames: function() {
// The model currently does not enforce a name to be present, but we're
// going to assume it is awaiting more advanced validation of required fields
// by the model. (See bug 1069918)
// NOTE: this method of finding a firstname and lastname is not i18n-proof.
let names = this.props.contact.name[0].split(" ");
return {
firstName: names.shift(),
lastName: names.join(" ")
};
},
getPreferredEmail: function() {
// The model currently does not enforce a name to be present, but we're
// going to assume it is awaiting more advanced validation of required fields
// by the model. (See bug 1069918)
let email = this.props.contact.email[0];
this.props.contact.email.some(function(address) {
if (address.pref) {
email = address;
return true;
}
return false;
});
return email;
},
render: function() {
let names = this.getContactNames();
let email = this.getPreferredEmail();
let cx = React.addons.classSet;
let contactCSSClass = cx({
contact: true,
blocked: this.props.contact.blocked
});
return (
<li onClick={this.handleContactClick} className={contactCSSClass}>
<div className="avatar">
<img src={navigator.mozLoop.getUserAvatar(email.value)} />
</div>
<div className="details">
<div className="username"><strong>{names.firstName}</strong> {names.lastName}
<i className={cx({"icon icon-google": this.props.contact.category[0] == "google"})} />
<i className={cx({"icon icon-blocked": this.props.contact.blocked})} />
</div>
<div className="email">{email.value}</div>
</div>
<div className="icons">
<i className="icon icon-video" />
<i className="icon icon-caret-down" />
</div>
</li>
);
}
});
const ContactsList = React.createClass({
getInitialState: function() {
return {
contacts: {}
};
},
componentDidMount: function() {
let contactsAPI = navigator.mozLoop.contacts;
contactsAPI.getAll((err, contacts) => {
if (err) {
throw err;
}
// Add contacts already present in the DB. We do this in timed chunks to
// circumvent blocking the main event loop.
let addContactsInChunks = () => {
contacts.splice(0, CONTACTS_CHUNK_SIZE).forEach(contact => {
this.handleContactAddOrUpdate(contact);
});
if (contacts.length) {
setTimeout(addContactsInChunks, 0);
}
};
addContactsInChunks(contacts);
// Listen for contact changes/ updates.
contactsAPI.on("add", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
contactsAPI.on("remove", (eventName, contact) => {
this.handleContactRemove(contact);
});
contactsAPI.on("removeAll", () => {
this.handleContactRemoveAll();
});
contactsAPI.on("update", (eventName, contact) => {
this.handleContactAddOrUpdate(contact);
});
});
},
handleContactAddOrUpdate: function(contact) {
let contacts = this.state.contacts;
let guid = String(contact._guid);
contacts[guid] = contact;
this.setState({});
},
handleContactRemove: function(contact) {
let contacts = this.state.contacts;
let guid = String(contact._guid);
if (!contacts[guid]) {
return;
}
delete contacts[guid];
this.setState({});
},
handleContactRemoveAll: function() {
this.setState({contacts: {}});
},
sortContacts: function(contact1, contact2) {
let comp = contact1.name[0].localeCompare(contact2.name[0]);
if (comp !== 0) {
return comp;
}
// If names are equal, compare against unique ids to make sure we have
// consistent ordering.
return contact1._guid - contact2._guid;
},
render: function() {
let viewForItem = item => {
return <ContactDetail key={item._guid} contact={item} />
};
let shownContacts = _.groupBy(this.state.contacts, function(contact) {
return contact.blocked ? "blocked" : "available";
});
return (
<div className="listWrapper">
<div ref="listSlider" className="listPanels">
<div className="faded">
<ul>
{shownContacts.available ?
shownContacts.available.sort(this.sortContacts).map(viewForItem) :
null}
{shownContacts.blocked ?
<h3 className="header">{mozL10n.get("contacts_blocked_contacts")}</h3> :
null}
{shownContacts.blocked ?
shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
null}
</ul>
</div>
</div>
</div>
);
}
});
return {
ContactsList: ContactsList
};
})(_, document.mozL10n);

View File

@ -14,6 +14,7 @@ loop.panel = (function(_, mozL10n) {
var sharedViews = loop.shared.views;
var sharedModels = loop.shared.models;
var sharedMixins = loop.shared.mixins;
var ContactsList = loop.contacts.ContactsList;
var __ = mozL10n.get; // aliasing translation function as __ for concision
/**
@ -498,7 +499,7 @@ loop.panel = (function(_, mozL10n) {
ToSView(null)
),
Tab({name: "contacts"},
React.DOM.span(null, "contacts")
ContactsList(null)
)
),
React.DOM.div({className: "footer"},

View File

@ -14,6 +14,7 @@ loop.panel = (function(_, mozL10n) {
var sharedViews = loop.shared.views;
var sharedModels = loop.shared.models;
var sharedMixins = loop.shared.mixins;
var ContactsList = loop.contacts.ContactsList;
var __ = mozL10n.get; // aliasing translation function as __ for concision
/**
@ -498,7 +499,7 @@ loop.panel = (function(_, mozL10n) {
<ToSView />
</Tab>
<Tab name="contacts">
<span>contacts</span>
<ContactsList />
</Tab>
</TabView>
<div className="footer">

View File

@ -9,6 +9,7 @@
<link rel="stylesheet" type="text/css" href="loop/shared/css/reset.css">
<link rel="stylesheet" type="text/css" href="loop/shared/css/common.css">
<link rel="stylesheet" type="text/css" href="loop/shared/css/panel.css">
<link rel="stylesheet" type="text/css" href="loop/shared/css/contacts.css">
</head>
<body class="panel">
@ -25,6 +26,7 @@
<script type="text/javascript" src="loop/shared/js/mixins.js"></script>
<script type="text/javascript" src="loop/shared/js/views.js"></script>
<script type="text/javascript" src="loop/js/client.js"></script>
<script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
<script type="text/javascript" src="loop/js/panel.js"></script>
</body>
</html>

View File

@ -395,3 +395,15 @@ p {
background: transparent url(../img/firefox-logo.png) no-repeat center center;
background-size: contain;
}
.header {
padding: 5px 10px;
color: #888;
margin: 0;
border-top: 1px solid #CCC;
background: #EEE;
display: flex;
align-items: center;
flex-direction: row;
height: 24px;
}

View File

@ -0,0 +1,163 @@
/* 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/. */
.contact {
display: flex;
flex-direction: row;
position: relative;
padding: 5px 10px;
color: #666;
font-size: 13px;
align-items: center;
}
.contact:not(:first-child) {
border-top: 1px solid #ddd;
}
.contact.blocked > .details > .username {
color: #d74345;
}
.contact:hover {
background: #eee;
}
.contact.selected {
background: #ebebeb;
}
.contact:hover > .icons {
display: block;
z-index: 1000;
}
.contact > .avatar {
width: 40px;
height: 40px;
background: #ccc;
border-radius: 50%;
margin-right: 10px;
overflow: hidden;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
background-image: url("../img/audio-call-avatar.svg");
background-repeat: no-repeat;
background-color: #4ba6e7;
background-size: contain;
}
.contact > .avatar > img {
width: 100%;
}
.contact > .details > .username {
font-size: 12px;
line-height: 20px;
color: #222;
font-weight: normal;
}
.contact > .details > .username > strong {
font-weight: bold;
}
.contact > .details > .username > i.icon-blocked {
display: inline-block;
width: 10px;
height: 20px;
-moz-margin-start: 3px;
background-image: url("../img/icons-16x16.svg#block-red");
background-position: center;
background-size: 10px 10px;
background-repeat: no-repeat;
}
.contact > .details > .username > i.icon-google {
position: absolute;
right: 10px;
top: 35%;
width: 14px;
height: 14px;
border-radius: 50%;
background-image: url("../img/icons-16x16.svg#google");
background-position: center;
background-size: 16px 16px;
background-repeat: no-repeat;
background-color: fff;
}
.contact > .details > .email {
color: #999;
font-size: 11px;
line-height: 16px;
}
.listWrapper {
overflow-x: hidden;
overflow-y: auto;
/* Show six contacts and scroll for the rest */
max-height: 305px;
}
.listPanels {
display: flex;
width: 200%;
flex-direction: row;
transition: 200ms ease-in;
transition-property: transform;
}
.listPanels > div {
flex: 0 0 50%;
}
.list {
display: flex;
flex-direction: column;
transition: opacity 0.3s ease-in-out;
}
.list.faded {
opacity: 0.3;
}
.list h3 {
margin: 0;
border-bottom: none;
border-top: 1px solid #ccc;
}
.icons {
cursor: pointer;
display: none;
margin-left: auto;
padding: 12px 10px;
border-radius: 30px;
background: #7ed321;
}
.icons:hover {
background: #89e029;
}
.icons i {
margin: 0 5px;
display: inline-block;
background-position: center;
background-repeat: no-repeat;
}
.icons i.icon-video {
background-image: url("../img/icons-14x14.svg#video-white");
background-size: 14px 14px;
width: 16px;
height: 16px;
}
.icons i.icon-caret-down {
background-image: url("../img/icons-10x10.svg#dropdown-white");
background-size: 10px 10px;
width: 10px;
height: 16px;
}

View File

@ -39,6 +39,7 @@
padding: 0 10px;
height: 16px;
cursor: pointer;
overflow: hidden;
background-repeat: no-repeat;
background-size: 16px 16px;
background-position: center;
@ -82,6 +83,7 @@
.share {
background: #fbfbfb;
margin-bottom: 14px;
}
.share .description,
@ -313,5 +315,4 @@ body[dir=rtl] .dropdown-menu-item {
background: #EAEAEA;
color: #7F7F7F;
padding: 14px;
margin-top: 14px;
}

View File

@ -0,0 +1,53 @@
<?xml version="1.0"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 0 10 10"
enable-background="new 0 0 10 10"
xml:space="preserve">
<style>
use:not(:target) {
display: none;
}
use {
fill: #ccc;
}
use[id$="-hover"] {
fill: #444;
}
use[id$="-active"] {
fill: #0095dd;
}
use[id$="-white"] {
fill: rgba(255, 255, 255, 0.8);
}
</style>
<defs style="display:none">
<polygon id="close-shape" fill-rule="evenodd" clip-rule="evenodd" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668
3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
<path id="dropdown-shape" fill-rule="evenodd" clip-rule="evenodd" d="M9,3L4.984,7L1,3H9z"/>
<polygon id="expand-shape" fill-rule="evenodd" clip-rule="evenodd" points="10,0 4.838,0 6.506,1.669 0,8.175 1.825,10 8.331,3.494
10,5.162"/>
<rect id="minimize-shape" y="3.6" fill-rule="evenodd" clip-rule="evenodd" width="10" height="2.8"/>
</defs>
<use id="close" xlink:href="#close-shape"/>
<use id="close-active" xlink:href="#close-shape"/>
<use id="close-disabled" xlink:href="#close-shape"/>
<use id="dropdown" xlink:href="#dropdown-shape"/>
<use id="dropdown-white" xlink:href="#dropdown-shape"/>
<use id="dropdown-active" xlink:href="#dropdown-shape"/>
<use id="dropdown-disabled" xlink:href="#dropdown-shape"/>
<use id="expand" xlink:href="#expand-shape"/>
<use id="expand-active" xlink:href="#expand-shape"/>
<use id="expand-disabled" xlink:href="#expand-shape"/>
<use id="minimize" xlink:href="#minimize-shape"/>
<use id="minimize-active" xlink:href="#minimize-shape"/>
<use id="minimize-disabled" xlink:href="#minimize-shape"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,134 @@
<?xml version="1.0"?>
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px" y="0px"
viewBox="0 0 14 14"
enable-background="new 0 0 14 14"
xml:space="preserve">
<style>
use:not(:target) {
display: none;
}
use {
fill: #ccc;
}
use[id$="-hover"] {
fill: #444;
}
use[id$="-active"] {
fill: #0095dd;
}
use[id$="-white"] {
fill: #fff;
}
</style>
<defs style="display:none">
<path id="audio-shape" fill-rule="evenodd" clip-rule="evenodd" d="M9.571,6.143v1.714c0,1.42-1.151,2.571-2.571,2.571
c-1.42,0-2.571-1.151-2.571-2.571V6.143H3.571v1.714c0,1.597,1.093,2.935,2.571,3.316v0.97H5.714c-0.56,0-1.034,0.358-1.211,0.857
h4.993c-0.177-0.499-0.651-0.857-1.211-0.857H7.857v-0.97c1.478-0.381,2.571-1.719,2.571-3.316V6.143H9.571z M7,10
c1.183,0,2.143-0.959,2.143-2.143V3.143C9.143,1.959,8.183,1,7,1C5.817,1,4.857,1.959,4.857,3.143v4.714C4.857,9.041,5.817,10,7,10
z"/>
<g id="facemute-shape">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.174,3.551L9.568,5.856V5.847L3.39,11.49h5.066
c0.613,0,1.111-0.533,1.111-1.19V8.526l2.606,2.304C12.4,11.071,12.71,11.142,13,11.078V3.302C12.71,3.239,12.4,3.309,12.174,3.551
z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.395,2.617l-0.001-0.001l-0.809-0.884l-2.102,1.92
C9.316,3.221,8.919,2.918,8.457,2.918H2.111C1.498,2.918,1,3.451,1,4.109v6.191c0,0.318,0.118,0.607,0.306,0.821l-0.288,0.263
l0.809,0.884l0.001,0.001l0.853-0.779l6.887-6.29L12.395,2.617z"/>
</g>
<path id="hangup-shape" fill-rule="evenodd" clip-rule="evenodd" d="M13,11.732c-0.602,0.52-1.254,0.946-1.941,1.267
c-1.825-0.337-4.164-1.695-6.264-3.795C2.696,7.106,1.339,4.769,1,2.945c0.321-0.688,0.748-1.341,1.268-1.944l2.528,2.855
C4.579,4.153,4.377,4.454,4.209,4.759L4.22,4.77C3.924,5.42,4.608,6.833,5.889,8.114c1.281,1.28,2.694,1.965,3.343,1.669
l0.011,0.011c0.305-0.168,0.606-0.37,0.904-0.587L13,11.732z"/>
<path id="incoming-shape" fill-rule="evenodd" clip-rule="evenodd" d="M2.745,7.558l0.637,0.669c0.04,0.041,0.085,0.073,0.134,0.1
l3.249,3.313c0.38,0.393,0.915,0.478,1.197,0.186l0.638-0.676c0.281-0.292,0.2-0.848-0.18-1.244L7.097,8.558h3.566
c0.419,0,0.759-0.34,0.759-0.759V6.28c0-0.419-0.34-0.759-0.759-0.759H7.059l1.42-1.443c0.381-0.392,0.461-0.945,0.18-1.234
l-0.637-0.67C7.74,1.883,7.204,1.966,6.824,2.359L3.55,5.688C3.487,5.717,3.43,5.755,3.381,5.806L2.745,6.482
c-0.131,0.137-0.183,0.332-0.162,0.54C2.562,7.229,2.613,7.423,2.745,7.558z"/>
<path id="link-shape" fill-rule="evenodd" clip-rule="evenodd" d="M7.359,6.107c0.757-0.757,0.757-1.995,0-2.752
L5.573,1.568c-0.757-0.757-1.995-0.757-2.752,0L1.568,2.82c-0.757,0.757-0.757,1.995,0,2.752l1.787,1.787
c0.757,0.757,1.995,0.757,2.752,0L6.266,7.2L6.8,7.734L6.641,7.893c-0.757,0.757-0.757,1.995,0,2.752l1.787,1.787
c0.757,0.757,1.995,0.757,2.752,0l1.253-1.253c0.757-0.757,0.757-1.995,0-2.752l-1.787-1.787c-0.757-0.757-1.995-0.757-2.752,0
L7.734,6.8L7.2,6.266L7.359,6.107z M9.87,7.868l1.335,1.335c0.294,0.294,0.294,0.774,0,1.068l-0.934,0.934
c-0.294,0.294-0.774,0.294-1.068,0L7.868,9.87c-0.294-0.294-0.294-0.774,0-1.068L8.13,9.064c0.294,0.294,0.744,0.324,1.001,0.067
C9.388,8.874,9.358,8.424,9.064,8.13L8.802,7.868C9.096,7.574,9.577,7.574,9.87,7.868z M4.13,6.132L2.795,4.797
c-0.294-0.294-0.294-0.774,0-1.068l0.934-0.934c0.294-0.294,0.774-0.294,1.068,0L6.132,4.13c0.294,0.294,0.294,0.774,0,1.068
L5.86,4.926C5.567,4.632,5.116,4.602,4.859,4.859C4.602,5.116,4.632,5.567,4.926,5.86l0.272,0.272
C4.904,6.426,4.423,6.426,4.13,6.132z"/>
<g id="mute-shape">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.186,9.492L5.49,9.188l3.822-3.822l2.354-2.354l-0.848-0.848
L9.312,3.669V3.142C9.312,1.959,8.352,1,7.169,1C5.986,1,5.026,1.959,5.026,3.142v4.715c0,0.032,0.001,0.064,0.002,0.096
L4.643,8.338c-0.03-0.156-0.046-0.317-0.046-0.481V6.142H3.741v1.715c0,0.414,0.073,0.81,0.208,1.176l-1.615,1.615l0.848,0.848
l1.398-1.398v0L5.186,9.492z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.312,7.857V6.045L5.829,9.528C6.196,9.824,6.662,10,7.169,10
C8.352,10,9.312,9.04,9.312,7.857z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.741,7.857c0,1.42-1.151,2.572-2.572,2.572
c-0.625,0-1.199-0.223-1.645-0.595l-0.605,0.605c0.395,0.344,0.87,0.599,1.393,0.734v0.97H5.884c-0.56,0-1.034,0.359-1.212,0.858
h4.994c-0.178-0.499-0.652-0.858-1.212-0.858H8.026v-0.97c1.478-0.38,2.572-1.718,2.572-3.316V6.142H9.741V7.857z"/>
</g>
<path id="pause-shape" fill-rule="evenodd" clip-rule="evenodd" d="M4.75,1h-1.5C2.836,1,2.5,1.336,2.5,1.75v10.5
C2.5,12.664,2.836,13,3.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C5.5,1.336,5.164,1,4.75,1z M10.75,1h-1.5
C8.836,1,8.5,1.336,8.5,1.75v10.5C8.5,12.664,8.836,13,9.25,13h1.5c0.414,0,0.75-0.336,0.75-0.75V1.75C11.5,1.336,11.164,1,10.75,1
z"/>
<path id="video-shape" fill-rule="evenodd" clip-rule="evenodd" d="M12.175,3.347L9.568,5.651V3.905c0-0.657-0.497-1.19-1.111-1.19
H2.111C1.498,2.714,1,3.247,1,3.905v6.191c0,0.658,0.498,1.19,1.111,1.19h6.345c0.614,0,1.111-0.533,1.111-1.19V8.322l2.607,2.305
C12.4,10.867,12.71,10.938,13,10.874V3.099C12.71,3.035,12.4,3.106,12.175,3.347z"/>
<g id="volume-shape">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.513,4.404H1.896c-0.417,0-0.756,0.338-0.756,0.755v3.679
c0,0.417,0.338,0.755,0.756,0.755H3.51l2.575,2.575c0.261,0.261,0.596,0.4,0.938,0.422V1.409C6.682,1.431,6.346,1.57,6.085,1.831
L3.513,4.404z M8.555,5.995C8.619,6.32,8.653,6.656,8.653,7c0,0.344-0.034,0.679-0.098,1.004l0.218,0.142
C8.852,7.777,8.895,7.393,8.895,7c0-0.394-0.043-0.777-0.123-1.147L8.555,5.995z M12.224,3.6l-0.475,0.31
c0.359,0.962,0.557,2.003,0.557,3.09c0,1.087-0.198,2.128-0.557,3.09l0.475,0.31c0.41-1.054,0.635-2.201,0.635-3.4
C12.859,5.8,12.634,4.654,12.224,3.6z M10.061,5.012C10.25,5.642,10.353,6.308,10.353,7c0,0.691-0.103,1.358-0.293,1.987
l0.351,0.229C10.634,8.517,10.756,7.772,10.756,7c0-0.773-0.121-1.517-0.345-2.216L10.061,5.012z"/>
<path d="M7.164,12.74l-0.15-0.009c-0.389-0.024-0.754-0.189-1.028-0.463L3.452,9.735H1.896
C1.402,9.735,1,9.333,1,8.838V5.16c0-0.494,0.402-0.896,0.896-0.896h1.558l2.531-2.531C6.26,1.458,6.625,1.293,7.014,1.269
l0.15-0.009V12.74z M1.896,4.545c-0.339,0-0.615,0.276-0.615,0.615v3.679c0,0.339,0.276,0.615,0.615,0.615h1.672l2.616,2.616
c0.19,0.19,0.434,0.316,0.697,0.363V1.568C6.619,1.615,6.375,1.741,6.185,1.931L3.571,4.545H1.896z M12.292,10.612l-0.714-0.467
l0.039-0.105C11.981,9.067,12.165,8.044,12.165,7c0-1.044-0.184-2.067-0.548-3.041l-0.039-0.105l0.714-0.467l0.063,0.162
C12.783,4.649,13,5.81,13,7s-0.217,2.351-0.645,3.451L12.292,10.612z M11.92,10.033l0.234,0.153
c0.374-1.019,0.564-2.09,0.564-3.186s-0.19-2.167-0.564-3.186L11.92,3.966C12.27,4.94,12.447,5.96,12.447,7
C12.447,8.04,12.27,9.059,11.92,10.033z M10.489,9.435L9.895,9.047l0.031-0.101C10.116,8.315,10.212,7.66,10.212,7
c0-0.661-0.096-1.316-0.287-1.947L9.895,4.952l0.594-0.388l0.056,0.176C10.779,5.471,10.897,6.231,10.897,7
c0,0.769-0.118,1.529-0.351,2.259L10.489,9.435z M10.225,8.926l0.106,0.069C10.52,8.348,10.615,7.677,10.615,7
c0-0.677-0.095-1.348-0.284-1.996l-0.106,0.07C10.403,5.699,10.494,6.347,10.494,7C10.494,7.652,10.403,8.3,10.225,8.926z
M8.867,8.376L8.398,8.07l0.018-0.093C8.48,7.654,8.512,7.325,8.512,7S8.48,6.345,8.417,6.022L8.398,5.929l0.469-0.306l0.043,0.2
C8.994,6.211,9.036,6.607,9.036,7c0,0.393-0.042,0.789-0.126,1.176L8.867,8.376z"/>
</g>
</defs>
<use id="audio" xlink:href="#audio-shape"/>
<use id="audio-active" xlink:href="#audio-shape"/>
<use id="audio-disabled" xlink:href="#audio-shape"/>
<use id="facemute" xlink:href="#facemute-shape"/>
<use id="facemute-active" xlink:href="#facemute-shape"/>
<use id="facemute-disabled" xlink:href="#facemute-shape"/>
<use id="hangup" xlink:href="#hangup-shape"/>
<use id="hangup-active" xlink:href="#hangup-shape"/>
<use id="hangup-disabled" xlink:href="#hangup-shape"/>
<use id="incoming" xlink:href="#incoming-shape"/>
<use id="incoming-active" xlink:href="#incoming-shape"/>
<use id="incoming-disabled" xlink:href="#incoming-shape"/>
<use id="link" xlink:href="#link-shape"/>
<use id="link-active" xlink:href="#link-shape"/>
<use id="link-disabled" xlink:href="#link-shape"/>
<use id="mute" xlink:href="#mute-shape"/>
<use id="mute-active" xlink:href="#mute-shape"/>
<use id="mute-disabled" xlink:href="#mute-shape"/>
<use id="pause" xlink:href="#pause-shape"/>
<use id="pause-active" xlink:href="#pause-shape"/>
<use id="pause-disabled" xlink:href="#pause-shape"/>
<use id="video" xlink:href="#video-shape"/>
<use id="video-white" xlink:href="#video-shape"/>
<use id="video-active" xlink:href="#video-shape"/>
<use id="video-disabled" xlink:href="#video-shape"/>
<use id="volume" xlink:href="#volume-shape"/>
<use id="volume-active" xlink:href="#volume-shape"/>
<use id="volume-disabled" xlink:href="#volume-shape"/>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -24,6 +24,10 @@ use[id$="-hover"] {
use[id$="-active"] {
fill: #0095dd;
}
use[id$="-red"] {
fill: #d74345
}
</style>
<defs style="display:none">
<path id="audio-shape" fill-rule="evenodd" clip-rule="evenodd" d="M11.429,6.857v2.286c0,1.894-1.535,3.429-3.429,3.429
@ -88,6 +92,7 @@ use[id$="-active"] {
<use id="audio-hover" xlink:href="#audio-shape"/>
<use id="audio-active" xlink:href="#audio-shape"/>
<use id="block" xlink:href="#block-shape"/>
<use id="block-red" xlink:href="#block-shape"/>
<use id="block-hover" xlink:href="#block-shape"/>
<use id="block-active" xlink:href="#block-shape"/>
<use id="contacts" xlink:href="#contacts-shape"/>

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -16,12 +16,14 @@ browser.jar:
content/browser/loop/js/conversation.js (content/js/conversation.js)
content/browser/loop/js/otconfig.js (content/js/otconfig.js)
content/browser/loop/js/panel.js (content/js/panel.js)
content/browser/loop/js/contacts.js (content/js/contacts.js)
# Shared styles
content/browser/loop/shared/css/reset.css (content/shared/css/reset.css)
content/browser/loop/shared/css/common.css (content/shared/css/common.css)
content/browser/loop/shared/css/panel.css (content/shared/css/panel.css)
content/browser/loop/shared/css/conversation.css (content/shared/css/conversation.css)
content/browser/loop/shared/css/contacts.css (content/shared/css/contacts.css)
# Shared images
content/browser/loop/shared/img/happy.png (content/shared/img/happy.png)
@ -46,6 +48,8 @@ browser.jar:
content/browser/loop/shared/img/svg/glyph-signin-16x16.svg (content/shared/img/svg/glyph-signin-16x16.svg)
content/browser/loop/shared/img/svg/glyph-signout-16x16.svg (content/shared/img/svg/glyph-signout-16x16.svg)
content/browser/loop/shared/img/audio-call-avatar.svg (content/shared/img/audio-call-avatar.svg)
content/browser/loop/shared/img/icons-10x10.svg (content/shared/img/icons-10x10.svg)
content/browser/loop/shared/img/icons-14x14.svg (content/shared/img/icons-14x14.svg)
content/browser/loop/shared/img/icons-16x16.svg (content/shared/img/icons-16x16.svg)
# Shared scripts

View File

@ -42,6 +42,7 @@
<script src="../../content/js/client.js"></script>
<script src="../../content/js/desktopRouter.js"></script>
<script src="../../content/js/conversation.js"></script>
<script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
<script src="../../content/js/panel.js"></script>
<!-- Test scripts -->

View File

@ -42,7 +42,13 @@ describe("loop.panel", function() {
getLoopCharPref: sandbox.stub().returns("unseen"),
copyString: sandbox.stub(),
noteCallUrlExpiry: sinon.spy(),
composeEmail: sinon.spy()
composeEmail: sinon.spy(),
contacts: {
getAll: function(callback) {
callback(null, []);
},
on: sandbox.stub()
}
};
document.mozL10n.initialize(navigator.mozLoop);

View File

@ -85,6 +85,9 @@ new_contact_button=New Contact
## and click the 'New Contact' button to see the fields.
new_contact_name_placeholder=Name
new_contact_email_placeholder=Email
contacts_blocked_contacts=Blocked Contacts
## LOCALIZATION NOTE (add_contact_button):
## This is the button to actually add the new contact
## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#contacts

View File

@ -403,7 +403,6 @@ pref("privacy.item.syncAccount", true);
// enable geo
pref("geo.enabled", true);
pref("app.geo.reportdata", 0);
// content sink control -- controls responsiveness during page load
// see https://bugzilla.mozilla.org/show_bug.cgi?id=481566#c9

View File

@ -1563,12 +1563,6 @@ public abstract class GeckoApp
}
});
PrefsHelper.getPref("app.geo.reportdata", new PrefsHelper.PrefHandlerBase() {
@Override public void prefValue(String pref, int value) {
// Acting on this pref is Bug 1036508; for now, do nothing.
}
});
// Trigger the completion of the telemetry timer that wraps activity startup,
// then grab the duration to give to FHR.
mJavaUiStartupTimer.stop();

View File

@ -110,14 +110,15 @@ OnSharedPreferenceChangeListener
private static final String PREFS_MENU_CHAR_ENCODING = "browser.menu.showCharacterEncoding";
private static final String PREFS_MP_ENABLED = "privacy.masterpassword.enabled";
private static final String PREFS_UPDATER_AUTODOWNLOAD = "app.update.autodownload";
private static final String PREFS_GEO_REPORTING = "app.geo.reportdata";
private static final String PREFS_GEO_REPORTING = NON_PREF_PREFIX + "app.geo.reportdata";
private static final String PREFS_GEO_LEARN_MORE = NON_PREF_PREFIX + "geo.learn_more";
private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode";
private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
private static final String PREFS_STUMBLER_ENABLED = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
// This isn't a Gecko pref, even if it looks like one.
private static final String PREFS_BROWSER_LOCALE = "locale";
@ -683,10 +684,9 @@ OnSharedPreferenceChangeListener
preferences.removePreference(pref);
i--;
continue;
} else if (AppConstants.RELEASE_BUILD &&
(PREFS_GEO_REPORTING.equals(key) ||
PREFS_GEO_LEARN_MORE.equals(key))) {
// We don't build wifi/cell tower collection in release builds, so hide the UI.
} else if (!AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED &&
(PREFS_GEO_REPORTING.equals(key) ||
PREFS_GEO_LEARN_MORE.equals(key))) {
preferences.removePreference(pref);
i--;
continue;
@ -869,10 +869,10 @@ OnSharedPreferenceChangeListener
/**
* Broadcast the provided value as the value of the
* <code>PREFS_STUMBLER_ENABLED</code> pref.
* <code>PREFS_GEO_REPORTING</code> pref.
*/
public static void broadcastStumblerPref(final Context context, final boolean value) {
Intent intent = new Intent(PREFS_STUMBLER_ENABLED)
Intent intent = new Intent(ACTION_STUMBLER_UPLOAD_PREF)
.putExtra("pref", PREFS_GEO_REPORTING)
.putExtra("branch", GeckoSharedPrefs.APP_PREFS_NAME)
.putExtra("enabled", value)
@ -888,7 +888,7 @@ OnSharedPreferenceChangeListener
/**
* Broadcast the current value of the
* <code>PREFS_STUMBLER_ENABLED</code> pref.
* <code>PREFS_GEO_REPORTING</code> pref.
*/
public static void broadcastStumblerPref(final Context context) {
final boolean value = getBooleanPref(context, PREFS_GEO_REPORTING, false);
@ -1321,22 +1321,7 @@ OnSharedPreferenceChangeListener
@Override
public void prefValue(String prefName, final int value) {
final Preference pref = getField(prefName);
final CheckBoxPrefSetter prefSetter;
if (PREFS_GEO_REPORTING.equals(prefName)) {
if (Versions.preICS) {
prefSetter = new CheckBoxPrefSetter();
} else {
prefSetter = new TwoStatePrefSetter();
}
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
prefSetter.setBooleanPref(pref, value == 1);
}
});
} else {
Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]");
}
Log.w(LOGTAG, "Unhandled int value for pref [" + pref + "]");
}
@Override

View File

@ -35,7 +35,7 @@
android:summary="@string/datareporting_crashreporter_summary"
android:defaultValue="false" />
<CheckBoxPreference android:key="app.geo.reportdata"
<CheckBoxPreference android:key="android.not_a_preference.app.geo.reportdata"
android:title="@string/datareporting_wifi_title"
android:summary="@string/datareporting_wifi_geolocation_summary" />

View File

@ -1,15 +1,11 @@
package org.mozilla.gecko.tests;
import org.json.JSONObject;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.tests.helpers.JavascriptBridge;
import org.mozilla.gecko.tests.helpers.JavascriptMessageParser;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONObject;
import org.mozilla.gecko.Actions;
import org.mozilla.gecko.Assert;
public class JavascriptTest extends BaseTest {
private static final String LOGTAG = "JavascriptTest";
@ -32,8 +28,7 @@ public class JavascriptTest extends BaseTest {
mActions.expectGeckoEvent(EVENT_TYPE);
mAsserter.dumpLog("Registered listener for " + EVENT_TYPE);
final String url = getAbsoluteUrl(StringHelper.ROBOCOP_JS_HARNESS_URL +
"?path=" + javascriptUrl);
final String url = getAbsoluteUrl(StringHelper.getHarnessUrlForJavascript(javascriptUrl));
mAsserter.dumpLog("Loading JavaScript test from " + url);
loadUrl(url);

View File

@ -104,7 +104,25 @@ public class StringHelper {
public static final String ROBOCOP_TEXT_PAGE_URL = "/robocop/robocop_text_page.html";
public static final String ROBOCOP_ADOBE_FLASH_URL = "/robocop/robocop_adobe_flash.html";
public static final String ROBOCOP_INPUT_URL = "/robocop/robocop_input.html";
public static final String ROBOCOP_JS_HARNESS_URL = "/robocop/robocop_javascript.html";
private static final String ROBOCOP_JS_HARNESS_URL = "/robocop/robocop_javascript.html";
/**
* Build a URL for loading a Javascript file in the Robocop Javascript
* harness.
* <p>
* We append a random slug to avoid caching: see
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache">https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache</a>.
*
* @param javascriptUrl to load.
* @return URL with harness wrapper.
*/
public static String getHarnessUrlForJavascript(String javascriptUrl) {
// We include a slug to make sure we never cache the harness.
return ROBOCOP_JS_HARNESS_URL +
"?slug=" + System.currentTimeMillis() +
"&path=" + javascriptUrl;
}
// Robocop page titles
public static final String ROBOCOP_BIG_LINK_TITLE = "Big Link";

View File

@ -18,7 +18,11 @@ function _evalURI(uri, sandbox) {
let theURI = SpecialPowers.Services.io
.newURI(uri, window.document.characterSet, baseURI);
req.open('GET', theURI.spec, false);
// We append a random slug to avoid caching: see
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache.
req.open('GET', theURI.spec + ((/\?/).test(theURI.spec) ? "&slug=" : "?slug=") + (new Date()).getTime(), false);
req.setRequestHeader('Cache-Control', 'no-cache');
req.setRequestHeader('Pragma', 'no-cache');
req.send();
return SpecialPowers.Cu.evalInSandbox(req.responseText, sandbox, "1.8", uri, 1);

View File

@ -59,8 +59,7 @@ public class testEventDispatcher extends UITest
public void testEventDispatcher() {
GeckoHelper.blockForReady();
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL +
"?path=" + TEST_JS);
NavigationHelper.enterAndLoadUrl(StringHelper.getHarnessUrlForJavascript(TEST_JS));
js.syncCall("send_test_message", GECKO_EVENT);
js.syncCall("send_message_for_response", GECKO_RESPONSE_EVENT, "success");

View File

@ -38,7 +38,7 @@ public class testGeckoRequest extends UITest {
public void testGeckoRequest() {
GeckoHelper.blockForReady();
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL + "?path=" + TEST_JS);
NavigationHelper.enterAndLoadUrl(StringHelper.getHarnessUrlForJavascript(TEST_JS));
// Register a listener for this request.
js.syncCall("add_request_listener", REQUEST_EVENT);

View File

@ -32,8 +32,7 @@ public class testJavascriptBridge extends UITest {
public void testJavascriptBridge() {
GeckoHelper.blockForReady();
NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL +
"?path=" + TEST_JS);
NavigationHelper.enterAndLoadUrl(StringHelper.getHarnessUrlForJavascript(TEST_JS));
js.syncCall("check_js_int_arg", 1);
}

View File

@ -170,12 +170,14 @@ public class testSettingsMenuItems extends PixelTest {
settingsMap.get(PATH_DISPLAY).remove(TITLE_BAR_LABEL_ARR);
}
// Anonymous cell tower/wifi collection - only built if *not* release build
String[] networkReportingUi = { "Mozilla Location Service", "Receives Wi-Fi and cellular location data when running in the background and shares it with Mozilla to improve our geolocation service" };
settingsMap.get(PATH_MOZILLA).add(networkReportingUi);
if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) {
// Anonymous cell tower/wifi collection
String[] networkReportingUi = { "Mozilla Location Service", "Receives Wi-Fi and cellular location data when running in the background and shares it with Mozilla to improve our geolocation service" };
settingsMap.get(PATH_MOZILLA).add(networkReportingUi);
String[] learnMoreUi = { "Learn more" };
settingsMap.get(PATH_MOZILLA).add(learnMoreUi);
String[] learnMoreUi = { "Learn more" };
settingsMap.get(PATH_MOZILLA).add(learnMoreUi);
}
}
// Automatic updates

View File

@ -78,7 +78,7 @@ var FindHelper = {
}
} else {
// Disabled until bug 1014113 is fixed
// ZoomHelper.zoomToRect(aData.rect);
//ZoomHelper.zoomToRect(aData.rect, -1, false, true);
this._viewportChanged = true;
}
}

View File

@ -81,16 +81,24 @@ var ZoomHelper = {
*/
zoomToElement: function(aElement, aClickY = -1, aCanZoomOut = true, aCanScrollHorizontally = true) {
let rect = ElementTouchHelper.getBoundingContentRect(aElement);
ZoomHelper.zoomToRect(rect, aClickY, aCanZoomOut, aCanScrollHorizontally, aElement);
},
zoomToRect: function(aRect, aClickY = -1, aCanZoomOut = true, aCanScrollHorizontally = true, aElement) {
const margin = 15;
if(!aRect.h || !aRect.w) {
aRect.h = aRect.height;
aRect.w = aRect.width;
}
let viewport = BrowserApp.selectedTab.getViewport();
rect = new Rect(aCanScrollHorizontally ? Math.max(viewport.cssPageLeft, rect.x - margin) : viewport.cssX,
rect.y,
aCanScrollHorizontally ? rect.w + 2 * margin : viewport.cssWidth,
rect.h);
let bRect = new Rect(aCanScrollHorizontally ? Math.max(viewport.cssPageLeft, aRect.x - margin) : viewport.cssX,
aRect.y,
aCanScrollHorizontally ? aRect.w + 2 * margin : viewport.cssWidth,
aRect.h);
// constrict the rect to the screen's right edge
rect.width = Math.min(rect.width, viewport.cssPageRight - rect.x);
bRect.width = Math.min(bRect.width, viewport.cssPageRight - bRect.x);
// if the rect is already taking up most of the visible area and is stretching the
// width of the page, then we want to zoom out instead.
@ -98,44 +106,37 @@ var ZoomHelper = {
if (BrowserEventHandler.mReflozPref) {
let zoomFactor = BrowserApp.selectedTab.getZoomToMinFontSize(aElement);
rect.width = zoomFactor <= 1.0 ? rect.width : gScreenWidth / zoomFactor;
rect.height = zoomFactor <= 1.0 ? rect.height : rect.height / zoomFactor;
if (zoomFactor == 1.0 || ZoomHelper.isRectZoomedIn(rect, viewport)) {
bRect.width = zoomFactor <= 1.0 ? bRect.width : gScreenWidth / zoomFactor;
bRect.height = zoomFactor <= 1.0 ? bRect.height : bRect.height / zoomFactor;
if (zoomFactor == 1.0 || ZoomHelper.isRectZoomedIn(bRect, viewport)) {
if (aCanZoomOut) {
ZoomHelper.zoomOut();
}
return;
}
} else if (ZoomHelper.isRectZoomedIn(rect, viewport)) {
} else if (ZoomHelper.isRectZoomedIn(bRect, viewport)) {
if (aCanZoomOut) {
ZoomHelper.zoomOut();
}
return;
}
ZoomHelper.zoomToRect(rect, aClickY);
}
},
/* Zoom to a specific part of the screen defined by a rect,
* optionally keeping a particular part of it in view
* if it is really tall.
*/
zoomToRect: function(aRect, aClickY = -1) {
let rect = new Rect(aRect.x,
aRect.y,
aRect.width,
Math.min(aRect.width * viewport.cssHeight / viewport.cssWidth, aRect.height));
let rect = {};
rect.type = "Browser:ZoomToRect";
rect.x = bRect.x;
rect.y = bRect.y;
rect.w = bRect.width;
rect.h = Math.min(bRect.width * viewport.cssHeight / viewport.cssWidth, bRect.height);
if (aClickY >= 0) {
// if the block we're zooming to is really tall, and we want to keep a particular
// part of it in view, then adjust the y-coordinate of the target rect accordingly.
// the 1.2 multiplier is just a little fuzz to compensate for aRect including horizontal
// the 1.2 multiplier is just a little fuzz to compensate for bRect including horizontal
// margins but not vertical ones.
let cssTapY = viewport.cssY + aClickY;
if ((aRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) {
if ((bRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) {
rect.y = cssTapY - (rect.h / 2);
}
}

View File

@ -86,5 +86,9 @@ if test ! "$RELEASE_BUILD"; then
MOZ_ANDROID_SHARE_OVERLAY=1
fi
# Don't enable the Mozilla Location Service stumbler.
# MOZ_ANDROID_MLS_STUMBLER=1
# Enable the Mozilla Location Service stumbler in Nightly.
if test "$NIGHTLY_BUILD"; then
MOZ_ANDROID_MLS_STUMBLER=1
else
MOZ_ANDROID_MLS_STUMBLER=
fi

View File

@ -850,11 +850,11 @@ class TreeMetadataEmitter(LoggingMixin):
filtered = m.active_tests(exists=False, disabled=True,
**self.info)
missing = [t['name'] for t in filtered if not os.path.exists(t['path'])]
if missing:
raise SandboxValidationError('Test manifest (%s) lists '
'test that does not exist: %s' % (
path, ', '.join(missing)), context)
missing = [t['name'] for t in filtered if not os.path.exists(t['path'])]
if missing:
raise SandboxValidationError('Test manifest (%s) lists '
'test that does not exist: %s' % (
path, ', '.join(missing)), context)
out_dir = mozpath.join(install_prefix, manifest_reldir)
if 'install-to-subdir' in defaults:

View File

@ -0,0 +1,4 @@
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']

View File

@ -0,0 +1,4 @@
[DEFAULT]
support-files = support/**
[missing.js]

View File

@ -501,6 +501,14 @@ class TestEmitterBasic(unittest.TestCase):
'lists test that does not exist: test_missing.html'):
self.read_topsrcdir(reader)
def test_test_manifest_missing_test_error_unfiltered(self):
"""Missing test files should result in error, even when the test list is not filtered."""
reader = self.reader('test-manifest-missing-test-file-unfiltered')
with self.assertRaisesRegexp(SandboxValidationError,
'lists test that does not exist: missing.js'):
self.read_topsrcdir(reader)
def test_ipdl_sources(self):
reader = self.reader('ipdl_sources')
objs = self.read_topsrcdir(reader)

View File

@ -2431,9 +2431,9 @@ this.AddonManagerPrivate = {
// Start a timer, record a simple measure of the time interval when
// timer.done() is called
simpleTimer: function(aName) {
let startTime = Date.now();
let startTime = Cu.now();
return {
done: () => this.recordSimpleMeasure(aName, Date.now() - startTime)
done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime))
};
},

View File

@ -1494,7 +1494,7 @@ XPIState.prototype = {
*/
getModTime(aFile, aId) {
let changed = false;
let scanStarted = Date.now();
let scanStarted = Cu.now();
// For an unknown or enabled add-on, we do a full recursive scan.
if (!('scanTime' in this) || this.enabled) {
logger.debug('getModTime: Recursive scan of ' + aId);
@ -1539,7 +1539,7 @@ XPIState.prototype = {
}
}
// Record duration of file-modified check
XPIProvider.setTelemetry(aId, "scan_MS", Date.now() - scanStarted);
XPIProvider.setTelemetry(aId, "scan_MS", Math.round(Cu.now() - scanStarted));
return changed;
},
@ -3539,10 +3539,10 @@ this.XPIProvider = {
}
// Telemetry probe added around getInstallState() to check perf
let telemetryCaptureTime = Date.now();
let telemetryCaptureTime = Cu.now();
let installChanged = XPIStates.getInstallState();
let telemetry = Services.telemetry;
telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Date.now() - telemetryCaptureTime);
telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Math.round(Cu.now() - telemetryCaptureTime));
if (installChanged) {
updateReasons.push("directoryState");
}