diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index d98dcc1401c0..674dd45c45fb 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "d717e2e751a6bb1303499bde6be9dbd88b275e30", + "revision": "846f8e7089361d42e1362ab591561f94d3bd31e9", "repo_path": "/integration/gaia-central" } diff --git a/browser/devtools/styleeditor/StyleEditorDebuggee.jsm b/browser/devtools/styleeditor/StyleEditorDebuggee.jsm index 5178686a5fc9..92295d3b156e 100644 --- a/browser/devtools/styleeditor/StyleEditorDebuggee.jsm +++ b/browser/devtools/styleeditor/StyleEditorDebuggee.jsm @@ -228,6 +228,12 @@ let StyleSheet = function(form, debuggee) { this._client.addListener("propertyChange", this._onPropertyChange); this._client.addListener("styleApplied", this._onStyleApplied); + // Backwards compatibility + this._client.addListener("sourceLoad-" + this._actor, this._onSourceLoad); + this._client.addListener("propertyChange-" + this._actor, this._onPropertyChange); + this._client.addListener("styleApplied-" + this._actor, this._onStyleApplied); + + // set initial property values for (let attr in form) { this[attr] = form[attr]; @@ -324,5 +330,9 @@ StyleSheet.prototype = { this._client.removeListener("sourceLoad", this._onSourceLoad); this._client.removeListener("propertyChange", this._onPropertyChange); this._client.removeListener("styleApplied", this._onStyleApplied); + + this._client.removeListener("sourceLoad-" + this._actor, this._onSourceLoad); + this._client.removeListener("propertyChange-" + this._actor, this._onPropertyChange); + this._client.removeListener("styleApplied-" + this._actor, this._onStyleApplied); } } diff --git a/dom/bluetooth/BluetoothHfpManager.cpp b/dom/bluetooth/BluetoothHfpManager.cpp index 4d6f3a069457..3fbc1d53596f 100644 --- a/dom/bluetooth/BluetoothHfpManager.cpp +++ b/dom/bluetooth/BluetoothHfpManager.cpp @@ -1270,6 +1270,7 @@ BluetoothHfpManager::GetNumberOfCalls(uint16_t aState) void BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex, uint16_t aCallState, + const nsAString& aError, const nsAString& aNumber, const bool aIsOutgoing, bool aSend) @@ -1391,7 +1392,6 @@ BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex, case nsITelephonyProvider::CALL_STATE_DISCONNECTED: switch (prevCallState) { case nsITelephonyProvider::CALL_STATE_INCOMING: - case nsITelephonyProvider::CALL_STATE_BUSY: // Incoming call, no break sStopSendingRingFlag = true; case nsITelephonyProvider::CALL_STATE_DIALING: @@ -1424,7 +1424,7 @@ BluetoothHfpManager::HandleCallStateChanged(uint32_t aCallIndex, GetNumberOfCalls(nsITelephonyProvider::CALL_STATE_DISCONNECTED)) { // In order to let user hear busy tone via connected Bluetooth headset, // we postpone the timing of dropping SCO. - if (prevCallState != nsITelephonyProvider::CALL_STATE_BUSY) { + if (!(aError.Equals(NS_LITERAL_STRING("BusyError")))) { DisconnectSco(); } else { // Close Sco later since Dialer is still playing busy tone via HF. diff --git a/dom/bluetooth/BluetoothHfpManager.h b/dom/bluetooth/BluetoothHfpManager.h index 6e2f805854bf..af623222d23a 100644 --- a/dom/bluetooth/BluetoothHfpManager.h +++ b/dom/bluetooth/BluetoothHfpManager.h @@ -86,8 +86,8 @@ public: * @param aSend A boolean indicates whether we need to notify headset or not */ void HandleCallStateChanged(uint32_t aCallIndex, uint16_t aCallState, - const nsAString& aNumber, const bool aIsOutgoing, - bool aSend); + const nsAString& aError, const nsAString& aNumber, + const bool aIsOutgoing, bool aSend); bool IsConnected(); bool IsScoConnected(); diff --git a/dom/bluetooth/BluetoothTelephonyListener.cpp b/dom/bluetooth/BluetoothTelephonyListener.cpp index e84d577e8e3c..b6f9f53b8d08 100644 --- a/dom/bluetooth/BluetoothTelephonyListener.cpp +++ b/dom/bluetooth/BluetoothTelephonyListener.cpp @@ -35,7 +35,7 @@ TelephonyListener::CallStateChanged(uint32_t aCallIndex, bool aIsEmergency) { BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); - hfp->HandleCallStateChanged(aCallIndex, aCallState, aNumber, + hfp->HandleCallStateChanged(aCallIndex, aCallState, EmptyString(), aNumber, aIsOutgoing, true); return NS_OK; @@ -57,7 +57,7 @@ TelephonyListener::EnumerateCallState(uint32_t aCallIndex, bool* aResult) { BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); - hfp->HandleCallStateChanged(aCallIndex, aCallState, aNumber, + hfp->HandleCallStateChanged(aCallIndex, aCallState, EmptyString(), aNumber, aIsOutgoing, false); *aResult = true; return NS_OK; @@ -77,7 +77,7 @@ TelephonyListener::NotifyError(int32_t aCallIndex, // via setting CALL_STATE_DISCONNECTED hfp->HandleCallStateChanged(aCallIndex, nsITelephonyProvider::CALL_STATE_DISCONNECTED, - EmptyString(), false, true); + aError, EmptyString(), false, true); NS_WARNING("Reset the call state due to call transition ends abnormally"); } diff --git a/dom/contacts/ContactManager.js b/dom/contacts/ContactManager.js index 7c79ef145159..cba2221aa5a2 100644 --- a/dom/contacts/ContactManager.js +++ b/dom/contacts/ContactManager.js @@ -640,6 +640,8 @@ ContactManager.prototype = { case "Contact:Save:Return:KO": case "Contact:Remove:Return:KO": case "Contacts:Clear:Return:KO": + case "Contacts:GetRevision:Return:KO": + case "Contacts:Count:Return:KO": req = this.getRequest(msg.requestID); if (req) Services.DOMRequest.fireError(req.request, msg.errorMsg); diff --git a/dom/contacts/fallback/ContactDB.jsm b/dom/contacts/fallback/ContactDB.jsm index 034794c4107b..f1d6b15558fc 100644 --- a/dom/contacts/fallback/ContactDB.jsm +++ b/dom/contacts/fallback/ContactDB.jsm @@ -93,284 +93,8 @@ ContactDB.prototype = { _dispatcher: {}, upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) { - if (DEBUG) debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!"); - let db = aDb; - let objectStore; - for (let currVersion = aOldVersion; currVersion < aNewVersion; currVersion++) { - if (currVersion == 0) { - /** - * Create the initial database schema. - * - * The schema of records stored is as follows: - * - * {id: "...", // UUID - * published: Date(...), // First published date. - * updated: Date(...), // Last updated date. - * properties: {...} // Object holding the ContactProperties - * } - */ - if (DEBUG) debug("create schema"); - objectStore = db.createObjectStore(STORE_NAME, {keyPath: "id"}); - - // Properties indexes - objectStore.createIndex("familyName", "properties.familyName", { multiEntry: true }); - objectStore.createIndex("givenName", "properties.givenName", { multiEntry: true }); - - objectStore.createIndex("familyNameLowerCase", "search.familyName", { multiEntry: true }); - objectStore.createIndex("givenNameLowerCase", "search.givenName", { multiEntry: true }); - objectStore.createIndex("telLowerCase", "search.tel", { multiEntry: true }); - objectStore.createIndex("emailLowerCase", "search.email", { multiEntry: true }); - } else if (currVersion == 1) { - if (DEBUG) debug("upgrade 1"); - - // Create a new scheme for the tel field. We move from an array of tel-numbers to an array of - // ContactTelephone. - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - // Delete old tel index. - if (objectStore.indexNames.contains("tel")) { - objectStore.deleteIndex("tel"); - } - - // Upgrade existing tel field in the DB. - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (DEBUG) debug("upgrade tel1: " + JSON.stringify(cursor.value)); - for (let number in cursor.value.properties.tel) { - cursor.value.properties.tel[number] = {number: number}; - } - cursor.update(cursor.value); - if (DEBUG) debug("upgrade tel2: " + JSON.stringify(cursor.value)); - cursor.continue(); - } - }; - - // Create new searchable indexes. - objectStore.createIndex("tel", "search.tel", { multiEntry: true }); - objectStore.createIndex("category", "properties.category", { multiEntry: true }); - } else if (currVersion == 2) { - if (DEBUG) debug("upgrade 2"); - // Create a new scheme for the email field. We move from an array of emailaddresses to an array of - // ContactEmail. - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - - // Delete old email index. - if (objectStore.indexNames.contains("email")) { - objectStore.deleteIndex("email"); - } - - // Upgrade existing email field in the DB. - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.email) { - if (DEBUG) debug("upgrade email1: " + JSON.stringify(cursor.value)); - cursor.value.properties.email = - cursor.value.properties.email.map(function(address) { return { address: address }; }); - cursor.update(cursor.value); - if (DEBUG) debug("upgrade email2: " + JSON.stringify(cursor.value)); - } - cursor.continue(); - } - }; - - // Create new searchable indexes. - objectStore.createIndex("email", "search.email", { multiEntry: true }); - } else if (currVersion == 3) { - if (DEBUG) debug("upgrade 3"); - - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - - // Upgrade existing impp field in the DB. - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.impp) { - if (DEBUG) debug("upgrade impp1: " + JSON.stringify(cursor.value)); - cursor.value.properties.impp = - cursor.value.properties.impp.map(function(value) { return { value: value }; }); - cursor.update(cursor.value); - if (DEBUG) debug("upgrade impp2: " + JSON.stringify(cursor.value)); - } - cursor.continue(); - } - }; - // Upgrade existing url field in the DB. - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.url) { - if (DEBUG) debug("upgrade url1: " + JSON.stringify(cursor.value)); - cursor.value.properties.url = - cursor.value.properties.url.map(function(value) { return { value: value }; }); - cursor.update(cursor.value); - if (DEBUG) debug("upgrade impp2: " + JSON.stringify(cursor.value)); - } - cursor.continue(); - } - }; - } else if (currVersion == 4) { - if (DEBUG) debug("Add international phone numbers upgrade"); - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.tel) { - if (DEBUG) debug("upgrade : " + JSON.stringify(cursor.value)); - cursor.value.properties.tel.forEach( - function(duple) { - let parsedNumber = PhoneNumberUtils.parse(duple.value.toString()); - if (parsedNumber) { - if (DEBUG) { - debug("InternationalFormat: " + parsedNumber.internationalFormat); - debug("InternationalNumber: " + parsedNumber.internationalNumber); - debug("NationalNumber: " + parsedNumber.nationalNumber); - debug("NationalFormat: " + parsedNumber.nationalFormat); - } - if (duple.value.toString() !== parsedNumber.internationalNumber) { - cursor.value.search.tel.push(parsedNumber.internationalNumber); - } - } else { - dump("Warning: No international number found for " + duple.value + "\n"); - } - } - ) - cursor.update(cursor.value); - } - if (DEBUG) debug("upgrade2 : " + JSON.stringify(cursor.value)); - cursor.continue(); - } - }; - } else if (currVersion == 5) { - if (DEBUG) debug("Add index for equals tel searches"); - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - - // Delete old tel index (not on the right field). - if (objectStore.indexNames.contains("tel")) { - objectStore.deleteIndex("tel"); - } - - // Create new index for "equals" searches - objectStore.createIndex("tel", "search.exactTel", { multiEntry: true }); - - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.tel) { - if (DEBUG) debug("upgrade : " + JSON.stringify(cursor.value)); - cursor.value.properties.tel.forEach( - function(duple) { - let number = duple.value.toString(); - let parsedNumber = PhoneNumberUtils.parse(number); - - cursor.value.search.exactTel = [number]; - if (parsedNumber && - parsedNumber.internationalNumber && - number !== parsedNumber.internationalNumber) { - cursor.value.search.exactTel.push(parsedNumber.internationalNumber); - } - } - ) - cursor.update(cursor.value); - } - if (DEBUG) debug("upgrade : " + JSON.stringify(cursor.value)); - cursor.continue(); - } - }; - } else if (currVersion == 6) { - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - let names = objectStore.indexNames; - let blackList = ["tel", "familyName", "givenName", "familyNameLowerCase", - "givenNameLowerCase", "telLowerCase", "category", "email", - "emailLowerCase"]; - for (var i = 0; i < names.length; i++) { - if (blackList.indexOf(names[i]) < 0) { - objectStore.deleteIndex(names[i]); - } - } - } else if (currVersion == 7) { - if (DEBUG) debug("Adding object store for cached searches"); - db.createObjectStore(SAVED_GETALL_STORE_NAME); - } else if (currVersion == 8) { - if (DEBUG) debug("Make exactTel only contain the value entered by the user"); - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.tel) { - cursor.value.search.exactTel = []; - cursor.value.properties.tel.forEach( - function(tel) { - let normalized = PhoneNumberUtils.normalize(tel.value.toString()); - cursor.value.search.exactTel.push(normalized); - } - ); - cursor.update(cursor.value); - } - cursor.continue(); - } - }; - } else if (currVersion == 9) { - // no-op, see https://bugzilla.mozilla.org/show_bug.cgi?id=883770#c16 - } else if (currVersion == 10) { - if (DEBUG) debug("Adding object store for database revision"); - db.createObjectStore(REVISION_STORE).put(0, REVISION_KEY); - } else if (currVersion == 11) { - if (DEBUG) debug("Add a telMatch index with national and international numbers"); - if (!objectStore) { - objectStore = aTransaction.objectStore(STORE_NAME); - } - if (!objectStore.indexNames.contains("telMatch")) { - objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true}); - } - objectStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (cursor.value.properties.tel) { - cursor.value.search.parsedTel = []; - cursor.value.properties.tel.forEach( - function(tel) { - let parsed = PhoneNumberUtils.parse(tel.value.toString()); - if (parsed) { - cursor.value.search.parsedTel.push(parsed.nationalNumber); - cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.nationalFormat)); - cursor.value.search.parsedTel.push(parsed.internationalNumber); - cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.internationalFormat)); - } - cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(tel.value.toString())); - } - ); - cursor.update(cursor.value); - } - cursor.continue(); - } - }; - } - - // Increment the DB revision on future schema changes as well - if (currVersion > 11) { - this.incrementRevision(aTransaction); - } - } - - // Add default contacts - if (aOldVersion == 0) { + function loadInitialContacts() { + // Add default contacts let jsm = {}; Cu.import("resource://gre/modules/FileUtils.jsm", jsm); Cu.import("resource://gre/modules/NetUtil.jsm", jsm); @@ -419,6 +143,334 @@ ContactDB.prototype = { objectStore.put(contact); } } + + if (DEBUG) debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!"); + let db = aDb; + let objectStore; + + let steps = [ + function upgrade0to1() { + /** + * Create the initial database schema. + * + * The schema of records stored is as follows: + * + * {id: "...", // UUID + * published: Date(...), // First published date. + * updated: Date(...), // Last updated date. + * properties: {...} // Object holding the ContactProperties + * } + */ + if (DEBUG) debug("create schema"); + objectStore = db.createObjectStore(STORE_NAME, {keyPath: "id"}); + + // Properties indexes + objectStore.createIndex("familyName", "properties.familyName", { multiEntry: true }); + objectStore.createIndex("givenName", "properties.givenName", { multiEntry: true }); + + objectStore.createIndex("familyNameLowerCase", "search.familyName", { multiEntry: true }); + objectStore.createIndex("givenNameLowerCase", "search.givenName", { multiEntry: true }); + objectStore.createIndex("telLowerCase", "search.tel", { multiEntry: true }); + objectStore.createIndex("emailLowerCase", "search.email", { multiEntry: true }); + next(); + }, + function upgrade1to2() { + if (DEBUG) debug("upgrade 1"); + + // Create a new scheme for the tel field. We move from an array of tel-numbers to an array of + // ContactTelephone. + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + // Delete old tel index. + if (objectStore.indexNames.contains("tel")) { + objectStore.deleteIndex("tel"); + } + + // Upgrade existing tel field in the DB. + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (DEBUG) debug("upgrade tel1: " + JSON.stringify(cursor.value)); + for (let number in cursor.value.properties.tel) { + cursor.value.properties.tel[number] = {number: number}; + } + cursor.update(cursor.value); + if (DEBUG) debug("upgrade tel2: " + JSON.stringify(cursor.value)); + cursor.continue(); + } else { + next(); + } + }; + + // Create new searchable indexes. + objectStore.createIndex("tel", "search.tel", { multiEntry: true }); + objectStore.createIndex("category", "properties.category", { multiEntry: true }); + }, + function upgrade2to3() { + if (DEBUG) debug("upgrade 2"); + // Create a new scheme for the email field. We move from an array of emailaddresses to an array of + // ContactEmail. + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + + // Delete old email index. + if (objectStore.indexNames.contains("email")) { + objectStore.deleteIndex("email"); + } + + // Upgrade existing email field in the DB. + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.email) { + if (DEBUG) debug("upgrade email1: " + JSON.stringify(cursor.value)); + cursor.value.properties.email = + cursor.value.properties.email.map(function(address) { return { address: address }; }); + cursor.update(cursor.value); + if (DEBUG) debug("upgrade email2: " + JSON.stringify(cursor.value)); + } + cursor.continue(); + } else { + next(); + } + }; + + // Create new searchable indexes. + objectStore.createIndex("email", "search.email", { multiEntry: true }); + }, + function upgrade3to4() { + if (DEBUG) debug("upgrade 3"); + + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + + // Upgrade existing impp field in the DB. + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.impp) { + if (DEBUG) debug("upgrade impp1: " + JSON.stringify(cursor.value)); + cursor.value.properties.impp = + cursor.value.properties.impp.map(function(value) { return { value: value }; }); + cursor.update(cursor.value); + if (DEBUG) debug("upgrade impp2: " + JSON.stringify(cursor.value)); + } + cursor.continue(); + } + }; + // Upgrade existing url field in the DB. + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.url) { + if (DEBUG) debug("upgrade url1: " + JSON.stringify(cursor.value)); + cursor.value.properties.url = + cursor.value.properties.url.map(function(value) { return { value: value }; }); + cursor.update(cursor.value); + if (DEBUG) debug("upgrade impp2: " + JSON.stringify(cursor.value)); + } + cursor.continue(); + } else { + next(); + } + }; + }, + function upgrade4to5() { + if (DEBUG) debug("Add international phone numbers upgrade"); + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.tel) { + if (DEBUG) debug("upgrade : " + JSON.stringify(cursor.value)); + cursor.value.properties.tel.forEach( + function(duple) { + let parsedNumber = PhoneNumberUtils.parse(duple.value.toString()); + if (parsedNumber) { + if (DEBUG) { + debug("InternationalFormat: " + parsedNumber.internationalFormat); + debug("InternationalNumber: " + parsedNumber.internationalNumber); + debug("NationalNumber: " + parsedNumber.nationalNumber); + debug("NationalFormat: " + parsedNumber.nationalFormat); + } + if (duple.value.toString() !== parsedNumber.internationalNumber) { + cursor.value.search.tel.push(parsedNumber.internationalNumber); + } + } else { + dump("Warning: No international number found for " + duple.value + "\n"); + } + } + ) + cursor.update(cursor.value); + } + if (DEBUG) debug("upgrade2 : " + JSON.stringify(cursor.value)); + cursor.continue(); + } else { + next(); + } + }; + }, + function upgrade5to6() { + if (DEBUG) debug("Add index for equals tel searches"); + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + + // Delete old tel index (not on the right field). + if (objectStore.indexNames.contains("tel")) { + objectStore.deleteIndex("tel"); + } + + // Create new index for "equals" searches + objectStore.createIndex("tel", "search.exactTel", { multiEntry: true }); + + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.tel) { + if (DEBUG) debug("upgrade : " + JSON.stringify(cursor.value)); + cursor.value.properties.tel.forEach( + function(duple) { + let number = duple.value.toString(); + let parsedNumber = PhoneNumberUtils.parse(number); + + cursor.value.search.exactTel = [number]; + if (parsedNumber && + parsedNumber.internationalNumber && + number !== parsedNumber.internationalNumber) { + cursor.value.search.exactTel.push(parsedNumber.internationalNumber); + } + } + ) + cursor.update(cursor.value); + } + if (DEBUG) debug("upgrade : " + JSON.stringify(cursor.value)); + cursor.continue(); + } else { + next(); + } + }; + }, + function upgrade6to7() { + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + let names = objectStore.indexNames; + let blackList = ["tel", "familyName", "givenName", "familyNameLowerCase", + "givenNameLowerCase", "telLowerCase", "category", "email", + "emailLowerCase"]; + for (var i = 0; i < names.length; i++) { + if (blackList.indexOf(names[i]) < 0) { + objectStore.deleteIndex(names[i]); + } + } + next(); + }, + function upgrade7to8() { + if (DEBUG) debug("Adding object store for cached searches"); + db.createObjectStore(SAVED_GETALL_STORE_NAME); + next(); + }, + function upgrade8to9() { + if (DEBUG) debug("Make exactTel only contain the value entered by the user"); + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.tel) { + cursor.value.search.exactTel = []; + cursor.value.properties.tel.forEach( + function(tel) { + let normalized = PhoneNumberUtils.normalize(tel.value.toString()); + cursor.value.search.exactTel.push(normalized); + } + ); + cursor.update(cursor.value); + } + cursor.continue(); + } else { + next(); + } + }; + }, + function upgrade9to10() { + // no-op, see https://bugzilla.mozilla.org/show_bug.cgi?id=883770#c16 + next(); + }, + function upgrade10to11() { + if (DEBUG) debug("Adding object store for database revision"); + db.createObjectStore(REVISION_STORE).put(0, REVISION_KEY); + next(); + }, + function upgrade11to12() { + if (DEBUG) debug("Add a telMatch index with national and international numbers"); + if (!objectStore) { + objectStore = aTransaction.objectStore(STORE_NAME); + } + if (!objectStore.indexNames.contains("telMatch")) { + objectStore.createIndex("telMatch", "search.parsedTel", {multiEntry: true}); + } + objectStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.properties.tel) { + cursor.value.search.parsedTel = []; + cursor.value.properties.tel.forEach( + function(tel) { + let parsed = PhoneNumberUtils.parse(tel.value.toString()); + if (parsed) { + cursor.value.search.parsedTel.push(parsed.nationalNumber); + cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.nationalFormat)); + cursor.value.search.parsedTel.push(parsed.internationalNumber); + cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(parsed.internationalFormat)); + } + cursor.value.search.parsedTel.push(PhoneNumberUtils.normalize(tel.value.toString())); + } + ); + cursor.update(cursor.value); + } + cursor.continue(); + } else { + next(); + } + }; + } + ]; + + let index = aOldVersion; + let outer = this; + function next() { + if (index == aNewVersion) { + if (aOldVersion === 0) { + loadInitialContacts(); + } + outer.incrementRevision(aTransaction); + return; + } + try { + var i = index++; + if (DEBUG) debug("Upgrade step: " + i + "\n"); + steps[i](); + } catch(ex) { + dump("Caught exception" + ex); + aTransaction.abort(); + return; + } + }; + if (aNewVersion > steps.length) { + dump("Contacts DB upgrade error!"); + aTransaction.abort(); + } + next(); }, makeImport: function makeImport(aContact) { @@ -739,22 +791,22 @@ ContactDB.prototype = { }.bind(this), aFailureCb); }, - getRevision: function CDB_getRevision(aSuccessCb) { + getRevision: function CDB_getRevision(aSuccessCb, aErrorCb) { if (DEBUG) debug("getRevision"); this.newTxn("readonly", REVISION_STORE, function (txn, store) { store.get(REVISION_KEY).onsuccess = function (e) { aSuccessCb(e.target.result); }; - }); + },null, aErrorCb); }, - getCount: function CDB_getCount(aSuccessCb) { + getCount: function CDB_getCount(aSuccessCb, aErrorCb) { if (DEBUG) debug("getCount"); this.newTxn("readonly", STORE_NAME, function (txn, store) { store.count().onsuccess = function (e) { aSuccessCb(e.target.result); }; - }); + }, null, aErrorCb); }, /* diff --git a/dom/contacts/fallback/ContactService.jsm b/dom/contacts/fallback/ContactService.jsm index 9bd5bc4dfb51..a4a04741a343 100644 --- a/dom/contacts/fallback/ContactService.jsm +++ b/dom/contacts/fallback/ContactService.jsm @@ -205,7 +205,8 @@ let ContactService = { requestID: msg.requestID, revision: revision }); - } + }, + function(aErrorMsg) { mm.sendAsyncMessage("Contacts:GetRevision:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this) ); break; case "Contacts:GetCount": @@ -218,7 +219,8 @@ let ContactService = { requestID: msg.requestID, count: count }); - } + }, + function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Count:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this) ); break; case "Contacts:RegisterForMessages": diff --git a/dom/contacts/tests/test_contacts_basics.html b/dom/contacts/tests/test_contacts_basics.html index 8d617dd47ed1..8d2afb1f9cbc 100644 --- a/dom/contacts/tests/test_contacts_basics.html +++ b/dom/contacts/tests/test_contacts_basics.html @@ -257,10 +257,12 @@ ok(mozContacts, "mozContacts exists"); ok("mozContact" in window, "mozContact exists"); var steps = [ function() { - mozContacts.getRevision().onsuccess = function(e) { + req = mozContacts.getRevision(); + req.onsuccess = function(e) { initialRev = e.target.result; next(); }; + req.onerror = onFailure; }, function () { ok(true, "Deleting database"); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 4a9a357ea572..caefdcbdbc98 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -730,19 +730,28 @@ ContentParent::TransformPreallocatedIntoApp(const nsAString& aAppManifestURL, } void -ContentParent::ShutDownProcess() +ContentParent::ShutDownProcess(bool aFromActorDestroyed) { if (!mIsDestroyed) { + mIsDestroyed = true; + const InfallibleTArray& idbParents = ManagedPIndexedDBParent(); for (uint32_t i = 0; i < idbParents.Length(); ++i) { static_cast(idbParents[i])->Disconnect(); } - // Close() can only be called once. It kicks off the - // destruction sequence. - Close(); - mIsDestroyed = true; + if (aFromActorDestroyed) { + // If ActorDestroyed() is calling this method but mIsDestroyed was false, + // we're in an error state. + AsyncChannel* channel = GetIPCChannel(); + if (channel) { + channel->CloseWithError(); + } + } else { + // Close() can only be called once: It kicks off the destruction sequence. + Close(); + } } // NB: must MarkAsDead() here so that this isn't accidentally // returned from Get*() while in the midst of shutdown. @@ -948,6 +957,11 @@ ContentParent::ActorDestroy(ActorDestroyReason why) obs->NotifyObservers((nsIPropertyBag2*) props, "ipc:content-shutdown", nullptr); } + // If the child process was terminated due to a SIGKIL, ShutDownProcess + // might not have been called yet. We must call it to ensure that our + // channel is closed, etc. + ShutDownProcess(/* aForce = */ true); + MessageLoop::current()-> PostTask(FROM_HERE, NewRunnableFunction(DelayedDeleteSubprocess, mSubprocess)); @@ -1006,7 +1020,8 @@ ContentParent::NotifyTabDestroyed(PBrowserParent* aTab, if (ManagedPBrowserParent().Length() == 1) { MessageLoop::current()->PostTask( FROM_HERE, - NewRunnableMethod(this, &ContentParent::ShutDownProcess)); + NewRunnableMethod(this, &ContentParent::ShutDownProcess, + /* force */ false)); } } @@ -1464,7 +1479,7 @@ ContentParent::Observe(nsISupports* aSubject, const PRUnichar* aData) { if (!strcmp(aTopic, "xpcom-shutdown") && mSubprocess) { - ShutDownProcess(); + ShutDownProcess(/* force */ false); NS_ASSERTION(!mSubprocess, "Close should have nulled mSubprocess"); } diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index cc68aa900125..7a60097a6eee 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -243,8 +243,14 @@ private: * will return false and this ContentParent will not be returned * by the Get*() funtions. However, the shutdown sequence itself * may be asynchronous. + * + * If aFromActorDestroyed is true and this is the first call to + * ShutDownProcess, then we'll close our channel using CloseWithError() + * rather than vanilla Close(). CloseWithError() indicates to IPC that this + * is an abnormal shutdown (e.g. a crash); when the process shuts down + * cleanly, ShutDownProcess runs before ActorDestroyed. */ - void ShutDownProcess(); + void ShutDownProcess(bool aFromActorDestroyed); PCompositorParent* AllocPCompositorParent(mozilla::ipc::Transport* aTransport, diff --git a/dom/messages/SystemMessagePermissionsChecker.jsm b/dom/messages/SystemMessagePermissionsChecker.jsm index 5d9243e65ddc..1dcb2561abb0 100644 --- a/dom/messages/SystemMessagePermissionsChecker.jsm +++ b/dom/messages/SystemMessagePermissionsChecker.jsm @@ -34,15 +34,6 @@ this.SystemMessagePermissionsTable = { "bluetooth-dialer-command": { "telephony": [] }, - "bluetooth-requestconfirmation": { - "bluetooth": [] - }, - "bluetooth-requestpasskey": { - "bluetooth": [] - }, - "bluetooth-requestpincode": { - "bluetooth": [] - }, "bluetooth-authorize": { "bluetooth": [] }, @@ -52,6 +43,9 @@ this.SystemMessagePermissionsTable = { "bluetooth-pairedstatuschanged": { "bluetooth": [] }, + "bluetooth-a2dp-status-changed": { + "bluetooth": [] + }, "bluetooth-hfp-status-changed": { "bluetooth": [] }, diff --git a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js index bad424f36e8d..9de371e10b76 100644 --- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js +++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js @@ -780,6 +780,11 @@ MobileMessageDatabaseService.prototype = { if (DEBUG) { debug("MMS: part[" + i + "]: " + JSON.stringify(part)); } + // Sometimes the part is incomplete because the device reboots when + // downloading MMS. Don't need to expose this part to the content. + if (!part) { + continue; + } let partHeaders = part["headers"]; let partContent = part["content"]; diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index 16d9bdaca00a..ef6bec53a110 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -194,8 +194,6 @@ function convertRILCallState(state) { case RIL.CALL_STATE_INCOMING: case RIL.CALL_STATE_WAITING: return nsITelephonyProvider.CALL_STATE_INCOMING; - case RIL.CALL_STATE_BUSY: - return nsITelephonyProvider.CALL_STATE_BUSY; default: throw new Error("Unknown rilCallState: " + state); } diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js index d77997f1dcbb..cc006a3ce9cf 100644 --- a/dom/system/gonk/ril_consts.js +++ b/dom/system/gonk/ril_consts.js @@ -442,7 +442,6 @@ this.CALL_STATE_DIALING = 2; this.CALL_STATE_ALERTING = 3; this.CALL_STATE_INCOMING = 4; this.CALL_STATE_WAITING = 5; -this.CALL_STATE_BUSY = 6; this.TOA_INTERNATIONAL = 0x91; this.TOA_UNKNOWN = 0x81; diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 5cda64cadec1..807a811853f7 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -4690,11 +4690,6 @@ RIL[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length case CALL_FAIL_NORMAL: this._handleDisconnectedCall(options); break; - case CALL_FAIL_BUSY: - options.state = CALL_STATE_BUSY; - this._handleChangedCallState(options); - this._handleDisconnectedCall(options); - break; default: options.rilMessageType = "callError"; options.errorMsg = RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[failCause]; diff --git a/dom/telephony/TelephonyCall.cpp b/dom/telephony/TelephonyCall.cpp index 7b0275af9a6c..d4785cb6b296 100644 --- a/dom/telephony/TelephonyCall.cpp +++ b/dom/telephony/TelephonyCall.cpp @@ -61,9 +61,6 @@ TelephonyCall::ChangeStateInternal(uint16_t aCallState, bool aFireEvents) case nsITelephonyProvider::CALL_STATE_ALERTING: stateString.AssignLiteral("alerting"); break; - case nsITelephonyProvider::CALL_STATE_BUSY: - stateString.AssignLiteral("busy"); - break; case nsITelephonyProvider::CALL_STATE_CONNECTING: stateString.AssignLiteral("connecting"); break; @@ -269,7 +266,6 @@ TelephonyCall::Resume() NS_IMPL_EVENT_HANDLER(TelephonyCall, statechange) NS_IMPL_EVENT_HANDLER(TelephonyCall, dialing) NS_IMPL_EVENT_HANDLER(TelephonyCall, alerting) -NS_IMPL_EVENT_HANDLER(TelephonyCall, busy) NS_IMPL_EVENT_HANDLER(TelephonyCall, connecting) NS_IMPL_EVENT_HANDLER(TelephonyCall, connected) NS_IMPL_EVENT_HANDLER(TelephonyCall, disconnecting) diff --git a/dom/telephony/nsIDOMTelephonyCall.idl b/dom/telephony/nsIDOMTelephonyCall.idl index 14ab2c1f89aa..3b6ccd3b5d30 100644 --- a/dom/telephony/nsIDOMTelephonyCall.idl +++ b/dom/telephony/nsIDOMTelephonyCall.idl @@ -8,7 +8,7 @@ interface nsIDOMEventListener; -[scriptable, builtinclass, uuid(22e44e8c-cb74-44f2-abe6-b37e9f42ea79)] +[scriptable, builtinclass, uuid(74d240f5-a379-4ac0-a085-a7f714189a27)] interface nsIDOMTelephonyCall : nsIDOMEventTarget { readonly attribute DOMString number; @@ -32,7 +32,6 @@ interface nsIDOMTelephonyCall : nsIDOMEventTarget [implicit_jscontext] attribute jsval ondialing; [implicit_jscontext] attribute jsval onalerting; - [implicit_jscontext] attribute jsval onbusy; [implicit_jscontext] attribute jsval onconnecting; [implicit_jscontext] attribute jsval onconnected; [implicit_jscontext] attribute jsval ondisconnecting; diff --git a/dom/telephony/nsITelephonyProvider.idl b/dom/telephony/nsITelephonyProvider.idl index ca94c1606b89..4917f7a34cee 100644 --- a/dom/telephony/nsITelephonyProvider.idl +++ b/dom/telephony/nsITelephonyProvider.idl @@ -83,15 +83,14 @@ interface nsITelephonyProvider : nsISupports const unsigned short CALL_STATE_UNKNOWN = 0; const unsigned short CALL_STATE_DIALING = 1; const unsigned short CALL_STATE_ALERTING = 2; - const unsigned short CALL_STATE_BUSY = 3; - const unsigned short CALL_STATE_CONNECTING = 4; - const unsigned short CALL_STATE_CONNECTED = 5; - const unsigned short CALL_STATE_HOLDING = 6; - const unsigned short CALL_STATE_HELD = 7; - const unsigned short CALL_STATE_RESUMING = 8; - const unsigned short CALL_STATE_DISCONNECTING = 9; - const unsigned short CALL_STATE_DISCONNECTED = 10; - const unsigned short CALL_STATE_INCOMING = 11; + const unsigned short CALL_STATE_CONNECTING = 3; + const unsigned short CALL_STATE_CONNECTED = 4; + const unsigned short CALL_STATE_HOLDING = 5; + const unsigned short CALL_STATE_HELD = 6; + const unsigned short CALL_STATE_RESUMING = 7; + const unsigned short CALL_STATE_DISCONNECTING = 8; + const unsigned short CALL_STATE_DISCONNECTED = 9; + const unsigned short CALL_STATE_INCOMING = 10; /** * Called when a content process registers receiving unsolicited messages from diff --git a/dom/telephony/test/marionette/test_outgoing_busy.js b/dom/telephony/test/marionette/test_outgoing_busy.js index 333110623ef6..c27d081fd7cc 100644 --- a/dom/telephony/test/marionette/test_outgoing_busy.js +++ b/dom/telephony/test/marionette/test_outgoing_busy.js @@ -92,10 +92,10 @@ function dial() { function busy() { log("The receiver is busy."); - outgoing.onbusy = function onbusy(event) { - log("Received 'busy' call event."); + outgoing.onerror = function onerror(event) { + log("Received 'error' call event."); is(outgoing, event.call); - is(outgoing.state, "busy"); + is(event.call.error.name, "BusyError"); runEmulatorCmd("gsm list", function(result) { log("Call list is now: " + result); @@ -103,6 +103,7 @@ function busy() { cleanUp(); }); }; + runEmulatorCmd("gsm busy " + number); }; diff --git a/mfbt/Move.h b/mfbt/Move.h index 590e2bab5b9e..b917a3289ea9 100644 --- a/mfbt/Move.h +++ b/mfbt/Move.h @@ -101,11 +101,11 @@ namespace mozilla { * One hint: if you're writing a move constructor where the type has members * that should be moved themselves, it's much nicer to write this: * - * C(MoveRef c) : x(c->x), y(c->y) { } + * C(MoveRef c) : x(Move(c->x)), y(Move(c->y)) { } * * than the equivalent: * - * C(MoveRef c) { new(&x) X(c->x); new(&y) Y(c->y); } + * C(MoveRef c) { new(&x) X(Move(c->x)); new(&y) Y(Move(c->y)); } * * especially since GNU C++ fails to notice that this does indeed initialize x * and y, which may matter if they're const. diff --git a/python/mozboot/mozboot/base.py b/python/mozboot/mozboot/base.py index 1605488915ab..7f2b915e8690 100644 --- a/python/mozboot/mozboot/base.py +++ b/python/mozboot/mozboot/base.py @@ -187,7 +187,7 @@ class BaseBootstrapper(object): info = self.check_output([hg, '--version']).splitlines()[0] - match = re.search('version ([^\)]+)', info) + match = re.search('version ([^\+\)]+)', info) if not match: print('ERROR: Unable to identify Mercurial version.') return True, False, None diff --git a/services/common/log4moz.js b/services/common/log4moz.js index 9bea1dfd10d9..93dfce674f31 100644 --- a/services/common/log4moz.js +++ b/services/common/log4moz.js @@ -36,6 +36,16 @@ this.Log4Moz = { 20: "DEBUG", 10: "TRACE", 0: "ALL" + }, + Numbers: { + "FATAL": 70, + "ERROR": 60, + "WARN": 50, + "INFO": 40, + "CONFIG": 30, + "DEBUG": 20, + "TRACE": 10, + "ALL": 0, } }, @@ -55,6 +65,7 @@ this.Log4Moz = { Formatter: Formatter, BasicFormatter: BasicFormatter, + StructuredFormatter: StructuredFormatter, Appender: Appender, DumpAppender: DumpAppender, @@ -109,10 +120,15 @@ this.Log4Moz = { * LogMessage * Encapsulates a single log event's data */ -function LogMessage(loggerName, level, message){ +function LogMessage(loggerName, level, message, params) { this.loggerName = loggerName; this.level = level; this.message = message; + this.params = params; + + // The _structured field will correspond to whether this message is to + // be interpreted as a structured message. + this._structured = this.params && this.params.action; this.time = Date.now(); } LogMessage.prototype = { @@ -123,8 +139,12 @@ LogMessage.prototype = { }, toString: function LogMsg_toString(){ - return "LogMessage [" + this.time + " " + this.level + " " + - this.message + "]"; + let msg = "LogMessage [" + this.time + " " + this.level + " " + + this.message; + if (this.params) { + msg += " " + JSON.stringify(this.params); + } + return msg + "]" } }; @@ -211,7 +231,39 @@ Logger.prototype = { this.updateAppenders(); }, - log: function Logger_log(level, string) { + /** + * Logs a structured message object. + * + * @param action + * (string) A message action, one of a set of actions known to the + * log consumer. + * @param params + * (object) Parameters to be included in the message. + * If _level is included as a key and the corresponding value + * is a number or known level name, the message will be logged + * at the indicated level. + */ + logStructured: function (action, params) { + if (!action) { + throw "An action is required when logging a structured message."; + } + if (!params) { + return this.log(this.level, undefined, {"action": action}); + } + if (typeof params != "object") { + throw "The params argument is required to be an object."; + } + + let level = params._level || this.level; + if ((typeof level == "string") && level in Log4Moz.Level.Numbers) { + level = Log4Moz.Level.Numbers[level]; + } + + params.action = action; + this.log(level, params._message, params); + }, + + log: function (level, string, params) { if (this.level > level) return; @@ -224,32 +276,32 @@ Logger.prototype = { continue; } if (!message) { - message = new LogMessage(this._name, level, string); + message = new LogMessage(this._name, level, string, params); } appender.append(message); } }, - fatal: function Logger_fatal(string) { - this.log(Log4Moz.Level.Fatal, string); + fatal: function (string, params) { + this.log(Log4Moz.Level.Fatal, string, params); }, - error: function Logger_error(string) { - this.log(Log4Moz.Level.Error, string); + error: function (string, params) { + this.log(Log4Moz.Level.Error, string, params); }, - warn: function Logger_warn(string) { - this.log(Log4Moz.Level.Warn, string); + warn: function (string, params) { + this.log(Log4Moz.Level.Warn, string, params); }, - info: function Logger_info(string) { - this.log(Log4Moz.Level.Info, string); + info: function (string, params) { + this.log(Log4Moz.Level.Info, string, params); }, - config: function Logger_config(string) { - this.log(Log4Moz.Level.Config, string); + config: function (string, params) { + this.log(Log4Moz.Level.Config, string, params); }, - debug: function Logger_debug(string) { - this.log(Log4Moz.Level.Debug, string); + debug: function (string, params) { + this.log(Log4Moz.Level.Debug, string, params); }, - trace: function Logger_trace(string) { - this.log(Log4Moz.Level.Trace, string); + trace: function (string, params) { + this.log(Log4Moz.Level.Trace, string, params); } }; @@ -314,8 +366,8 @@ LoggerRepository.prototype = { /* * Formatters - * These massage a LogMessage into whatever output is desired - * Only the BasicFormatter is currently implemented + * These massage a LogMessage into whatever output is desired. + * BasicFormatter and StructuredFormatter are implemented here. */ // Abstract formatter @@ -324,7 +376,7 @@ Formatter.prototype = { format: function Formatter_format(message) {} }; -// Basic formatter that doesn't do anything fancy +// Basic formatter that doesn't do anything fancy. function BasicFormatter(dateFormat) { if (dateFormat) this.dateFormat = dateFormat; @@ -340,6 +392,36 @@ BasicFormatter.prototype = { } }; +// Structured formatter that outputs JSON based on message data. +// This formatter will format unstructured messages by supplying +// default values. +function StructuredFormatter() { } +StructuredFormatter.prototype = { + __proto__: Formatter.prototype, + + format: function (logMessage) { + let output = { + _time: logMessage.time, + _namespace: logMessage.loggerName, + _level: logMessage.levelDesc + }; + + for (let key in logMessage.params) { + output[key] = logMessage.params[key]; + } + + if (!output.action) { + output.action = "UNKNOWN"; + } + + if (!output._message && logMessage.message) { + output._message = logMessage.message; + } + + return JSON.stringify(output); + } +} + /* * Appenders * These can be attached to Loggers to log to different places diff --git a/services/common/tests/unit/test_log4moz.js b/services/common/tests/unit/test_log4moz.js index 5ee6adf77c16..76479ae95645 100644 --- a/services/common/tests/unit/test_log4moz.js +++ b/services/common/tests/unit/test_log4moz.js @@ -41,8 +41,9 @@ add_test(function test_Logger() { log.debug("this should be logged but not appended."); do_check_eq(appender.messages.length, 1); - do_check_true(appender.messages[0].indexOf("info test") > 0); - do_check_true(appender.messages[0].indexOf("INFO") > 0); + + let msgRe = /\d+\ttest.logger\t\INFO\tinfo test/; + do_check_true(msgRe.test(appender.messages[0])); run_next_test(); }); @@ -69,6 +70,113 @@ add_test(function test_Logger_parent() { run_next_test(); }); +// A utility method for checking object equivalence. +// Fields with a reqular expression value in expected will be tested +// against the corresponding value in actual. Otherwise objects +// are expected to have the same keys and equal values. +function checkObjects(expected, actual) { + do_check_true(expected instanceof Object); + do_check_true(actual instanceof Object); + for (let key in expected) { + do_check_neq(actual[key], undefined); + if (expected[key] instanceof RegExp) { + do_check_true(expected[key].test(actual[key].toString())); + } else { + if (expected[key] instanceof Object) { + checkObjects(expected[key], actual[key]); + } else { + do_check_eq(expected[key], actual[key]); + } + } + } + + for (let key in actual) { + do_check_neq(expected[key], undefined); + } +} + +add_test(function test_StructuredLogCommands() { + let appender = new MockAppender(new Log4Moz.StructuredFormatter()); + let logger = Log4Moz.repository.getLogger("test.StructuredOutput"); + logger.addAppender(appender); + logger.level = Log4Moz.Level.Info; + + logger.logStructured("test_message", {_message: "message string one"}); + logger.logStructured("test_message", {_message: "message string two", + _level: "ERROR", + source_file: "test_log4moz.js"}); + logger.logStructured("test_message"); + logger.logStructured("test_message", {source_file: "test_log4moz.js", + message_position: 4}); + + let messageOne = {"_time": /\d+/, + "_namespace": "test.StructuredOutput", + "_level": "INFO", + "_message": "message string one", + "action": "test_message"}; + + let messageTwo = {"_time": /\d+/, + "_namespace": "test.StructuredOutput", + "_level": "ERROR", + "_message": "message string two", + "action": "test_message", + "source_file": "test_log4moz.js"}; + + let messageThree = {"_time": /\d+/, + "_namespace": "test.StructuredOutput", + "_level": "INFO", + "action": "test_message"}; + + let messageFour = {"_time": /\d+/, + "_namespace": "test.StructuredOutput", + "_level": "INFO", + "action": "test_message", + "source_file": "test_log4moz.js", + "message_position": 4}; + + checkObjects(messageOne, JSON.parse(appender.messages[0])); + checkObjects(messageTwo, JSON.parse(appender.messages[1])); + checkObjects(messageThree, JSON.parse(appender.messages[2])); + checkObjects(messageFour, JSON.parse(appender.messages[3])); + + let errored = false; + try { + logger.logStructured("", {_message: "invalid message"}); + } catch (e) { + errored = true; + do_check_eq(e, "An action is required when logging a structured message."); + } finally { + do_check_true(errored); + } + + let errored = false; + try { + logger.logStructured("message_action", "invalid params"); + } catch (e) { + errored = true; + do_check_eq(e, "The params argument is required to be an object."); + } finally { + do_check_true(errored); + } + + // Logging with unstructured interface should produce the same messages + // as the structured interface for these cases. + let appender = new MockAppender(new Log4Moz.StructuredFormatter()); + let logger = Log4Moz.repository.getLogger("test.StructuredOutput1"); + messageOne._namespace = "test.StructuredOutput1"; + messageTwo._namespace = "test.StructuredOutput1"; + logger.addAppender(appender); + logger.level = Log4Moz.Level.All; + logger.info("message string one", {action: "test_message"}); + logger.error("message string two", {action: "test_message", + source_file: "test_log4moz.js"}); + + checkObjects(messageOne, JSON.parse(appender.messages[0])); + checkObjects(messageTwo, JSON.parse(appender.messages[1])); + + run_next_test(); +}); + add_test(function test_StorageStreamAppender() { let appender = new Log4Moz.StorageStreamAppender(testFormatter); do_check_eq(appender.getInputStream(), null);