diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 5cb57a197c6..a5f5067bb13 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 3b58324a38b..8b95afb7957 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml
index 04cf578e125..46bfd01cd60 100644
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 5cb57a197c6..a5f5067bb13 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml
index 2acd82307c7..10a1ffc9c92 100644
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -18,7 +18,7 @@
-
+
@@ -120,7 +120,7 @@
-
+
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index b008f5a7782..f4c61ba943f 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
- "revision": "7e5b2c297555985ac76b069361ff252fe176a018",
+ "revision": "896e4c28c82da3f07a1910be2187201e2045ffd7",
"repo_path": "/integration/gaia-central"
}
diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml
index 7ea97d8d1d1..b8e425e5cd9 100644
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml
index 8bc6be28ac0..e00426cb1bf 100644
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml
index bb60d8acc49..2f0fbae49cc 100644
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml
index b40be21ab91..e3796d252f7 100644
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml
index ea82de49412..3d0f60682bd 100644
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml
index bb3f09f03bb..9ac90efe0a6 100644
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/browser/devtools/webide/content/details.xhtml b/browser/devtools/webide/content/details.xhtml
index 0396e4dc0a3..29223696a12 100644
--- a/browser/devtools/webide/content/details.xhtml
+++ b/browser/devtools/webide/content/details.xhtml
@@ -5,7 +5,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
%webideDTD;
]>
diff --git a/browser/devtools/webide/content/jar.mn b/browser/devtools/webide/content/jar.mn
index 53c7d5b2525..70442b40506 100644
--- a/browser/devtools/webide/content/jar.mn
+++ b/browser/devtools/webide/content/jar.mn
@@ -10,3 +10,9 @@ webide.jar:
content/newapp.js (newapp.js)
content/details.xhtml (details.xhtml)
content/details.js (details.js)
+
+# Temporarily include locales in content, until we're ready
+# to localize webide
+
+ content/webide.dtd (../locales/en-US/webide.dtd)
+ content/webide.properties (../locales/en-US/webide.properties)
diff --git a/browser/devtools/webide/content/newapp.xul b/browser/devtools/webide/content/newapp.xul
index 8749a4e6950..31919cf217e 100644
--- a/browser/devtools/webide/content/newapp.xul
+++ b/browser/devtools/webide/content/newapp.xul
@@ -5,7 +5,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
%webideDTD;
]>
diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js
index 31396d83b29..51e7d9cfa2a 100644
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -15,7 +15,7 @@ const {AppProjects} = require("devtools/app-manager/app-projects");
const {Connection} = require("devtools/client/connection-manager");
const {AppManager} = require("devtools/app-manager");
-const Strings = Services.strings.createBundle("chrome://webide/locale/webide.properties");
+const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
const HTML = "http://www.w3.org/1999/xhtml";
diff --git a/browser/devtools/webide/content/webide.xul b/browser/devtools/webide/content/webide.xul
index 99efc5295ee..052fdf97742 100644
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -5,7 +5,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
%webideDTD;
]>
diff --git a/browser/devtools/webide/locales/Makefile.in b/browser/devtools/webide/locales/Makefile.in
deleted file mode 100644
index 82f614ecea2..00000000000
--- a/browser/devtools/webide/locales/Makefile.in
+++ /dev/null
@@ -1,5 +0,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/.
-
-DEFINES += -DAB_CD=$(AB_CD)
diff --git a/browser/devtools/webide/locales/jar.mn b/browser/devtools/webide/locales/jar.mn
deleted file mode 100644
index dc98d15f6b9..00000000000
--- a/browser/devtools/webide/locales/jar.mn
+++ /dev/null
@@ -1,10 +0,0 @@
-#filter substitution
-# 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/.
-
-
-webide.jar:
-% locale webide @AB_CD@ %locale/
- locale/webide.dtd (%webide.dtd)
- locale/webide.properties (%webide.properties)
diff --git a/browser/devtools/webide/locales/moz.build b/browser/devtools/webide/locales/moz.build
deleted file mode 100644
index c97072bba2d..00000000000
--- a/browser/devtools/webide/locales/moz.build
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
diff --git a/browser/devtools/webide/moz.build b/browser/devtools/webide/moz.build
index 89146c9212d..aa25425be48 100644
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -6,7 +6,6 @@
PARALLEL_DIRS += [
'content',
- 'locales',
'themes',
]
diff --git a/browser/themes/windows/browser-aero.css b/browser/themes/windows/browser-aero.css
index 86188a52a9b..b7ee8c9b524 100644
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -422,7 +422,11 @@
background-image: url("chrome://browser/skin/privatebrowsing-mask-tabstrip-XPVista7.png");
}
- #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ /* We're intentionally using the titlebar asset here for fullscreen mode.
+ * See bug 1008183.
+ */
+ #private-browsing-indicator-titlebar > .private-browsing-indicator,
+ #main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png");
}
}
diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css
index a0eabd01b80..cc96a9f6668 100644
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2855,7 +2855,7 @@ chatbox {
display: block;
}
-#main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) #TabsToolbar > .private-browsing-indicator {
+#main-window[privatebrowsingmode=temporary]:-moz-any([inFullscreen],:not([tabsintitlebar])) #TabsToolbar > .private-browsing-indicator {
display: -moz-box;
}
@@ -2865,7 +2865,12 @@ chatbox {
width: 48px;
}
-#private-browsing-indicator-titlebar > .private-browsing-indicator {
+/* Bug 1008183: We're intentionally using the titlebar asset here for fullscreen
+ * mode, since the tabstrip "mimics" the titlebar in that case with its own
+ * min/max/close window buttons.
+ */
+#private-browsing-indicator-titlebar > .private-browsing-indicator,
+#main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
background: url("chrome://browser/skin/privatebrowsing-mask-titlebar.png") no-repeat center 0px;
-moz-margin-end: 4px;
width: 40px;
@@ -2883,6 +2888,14 @@ chatbox {
background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7-tall.png");
height: 28px;
}
+
+ /* We're intentionally using the titlebar asset here for fullscreen mode.
+ * See bug 1008183.
+ */
+ #main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png");
+ }
+
#main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
top: -5px;
}
@@ -2893,7 +2906,11 @@ chatbox {
%endif
@media (-moz-windows-classic) {
- #private-browsing-indicator-titlebar > .private-browsing-indicator {
+ /* We're intentionally using the titlebar asset here for fullscreen mode.
+ * See bug 1008183.
+ */
+ #private-browsing-indicator-titlebar > .private-browsing-indicator,
+ #main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png");
}
/**
diff --git a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
index f41774e735e..ba27c9c8279 100644
--- a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
+++ b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
@@ -144,7 +144,10 @@ this.Address = {
if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null)
|| ((result = str.match(this.REGEXP_DECODE_IPV4)) != null)
|| ((result = str.match(this.REGEXP_DECODE_IPV6)) != null)
- || ((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)) {
+ || (((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)
+ && (result[2] != "PLMN")
+ && (result[2] != "IPv4")
+ && (result[2] != "IPv6"))) {
return {address: result[1], type: result[2]};
}
@@ -153,10 +156,7 @@ this.Address = {
type = "num";
} else if (str.match(this.REGEXP_ALPHANUM)) {
type = "alphanum";
- } else if (str.indexOf("@") > 0) {
- // E-mail should match the definition of `mailbox` as described in section
- // 3.4 of RFC2822, but excluding the obsolete definitions as indicated by
- // the "obs-" prefix. Here we match only a `@` character.
+ } else if (str.match(this.REGEXP_EMAIL)) {
type = "email";
} else {
throw new WSP.CodeError("Address: invalid address");
@@ -179,7 +179,7 @@ this.Address = {
let str;
switch (value.type) {
case "email":
- if (value.address.indexOf("@") > 0) {
+ if (value.address.match(this.REGEXP_EMAIL)) {
str = value.address;
}
break;
@@ -212,7 +212,7 @@ this.Address = {
if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE)
&& value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) {
str = value.address + "/TYPE=" + value.type;
- }
+ }
break;
}
@@ -234,11 +234,11 @@ this.Address = {
return "email";
}
- if (address.match(this.REGEXP_IPV4)) {
+ if (address.match(this.REGEXP_ENCODE_IPV4)) {
return "IPv4";
}
- if (address.match(this.REGEXP_IPV6)) {
+ if (address.match(this.REGEXP_ENCODE_IPV6)) {
return "IPv6";
}
@@ -252,20 +252,53 @@ this.Address = {
};
defineLazyRegExp(Address, "REGEXP_DECODE_PLMN", "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$");
-defineLazyRegExp(Address, "REGEXP_DECODE_IPV4", "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$");
-defineLazyRegExp(Address, "REGEXP_DECODE_IPV6", "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$");
+defineLazyRegExp(Address, "REGEXP_DECODE_IPV4", "^((?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9]))\\/TYPE=(IPv4)$");
+defineLazyRegExp(Address, "REGEXP_DECODE_IPV6", "^(" +
+ "(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,7}:|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|" +
+ "[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|" +
+ ":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])" +
+ ")\\/TYPE=(IPv6)$");
defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM", "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$");
defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN", "^\\+?[\\d.-]+$");
-defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
-defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
+defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4", "^(?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])$");
+defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6", "^(?:" +
+ "(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,7}:|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|" +
+ "(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|" +
+ "[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|" +
+ ":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)" +
+ ")$");
defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$");
defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$");
-defineLazyRegExp(Address, "REGEXP_NUM", "^[\\+*#]\\d+$");
+defineLazyRegExp(Address, "REGEXP_NUM", "^[\\+*#]?\\d+$");
defineLazyRegExp(Address, "REGEXP_ALPHANUM", "^\\w+$");
-defineLazyRegExp(Address, "REGEXP_PLMN", "^\\?[\\d.-]$");
-defineLazyRegExp(Address, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
-defineLazyRegExp(Address, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
-defineLazyRegExp(Address, "REGEXP_EMAIL", "@");
+// OMA-TS-MMS_ENC-V1_3-20110913-A chapter 8:
+//
+// E-mail should match the definition of `mailbox` as described in section
+// 3.4 of RFC2822, but excluding the obsolete definitions as indicated by
+// the "obs-" prefix.
+//
+// Here we try to match addr-spec only.
+defineLazyRegExp(Address, "REGEXP_EMAIL", "(?:" +
+ "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|" +
+ "\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\"" +
+ ")@(?:" +
+ "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|" +
+ "\\[(?:" +
+ "[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f]" +
+ ")*\\]" +
+ ")");
/**
* Header-field = MMS-header | Application-header
diff --git a/dom/mobilemessage/src/gonk/MmsService.js b/dom/mobilemessage/src/gonk/MmsService.js
index ca893d49cde..4e1106f4136 100644
--- a/dom/mobilemessage/src/gonk/MmsService.js
+++ b/dom/mobilemessage/src/gonk/MmsService.js
@@ -2007,9 +2007,10 @@ MmsService.prototype = {
// |aMessage.headers|
let headers = aMessage["headers"] = {};
+
let receivers = aParams.receivers;
+ let headersTo = headers["to"] = [];
if (receivers.length != 0) {
- let headersTo = headers["to"] = [];
for (let i = 0; i < receivers.length; i++) {
let receiver = receivers[i];
let type = MMS.Address.resolveType(receiver);
@@ -2023,8 +2024,10 @@ MmsService.prototype = {
"from " + receiver + " to " + address);
} else {
address = receiver;
- isAddrValid = false;
- if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
+ if (type == "Others") {
+ isAddrValid = false;
+ if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
+ }
}
headersTo.push({"address": address, "type": type});
}
diff --git a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
index 5536b4ba4d2..2322e1646df 100644
--- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -22,8 +22,8 @@ const RIL_GETTHREADSCURSOR_CID =
const DEBUG = false;
const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
+const DB_VERSION = 23;
-const DB_VERSION = 22;
const MESSAGE_STORE_NAME = "sms";
const THREAD_STORE_NAME = "thread";
const PARTICIPANT_STORE_NAME = "participant";
@@ -142,8 +142,12 @@ MobileMessageDB.prototype = {
let currentVersion = event.oldVersion;
function update(currentVersion) {
- let next = update.bind(self, currentVersion + 1);
+ if (currentVersion >= self.dbVersion) {
+ if (DEBUG) debug("Upgrade finished.");
+ return;
+ }
+ let next = update.bind(self, currentVersion + 1);
switch (currentVersion) {
case 0:
if (DEBUG) debug("New database");
@@ -245,8 +249,8 @@ MobileMessageDB.prototype = {
self.upgradeSchema21(db, event.target.transaction, next);
break;
case 22:
- // This will need to be moved for each new version
- if (DEBUG) debug("Upgrade finished.");
+ if (DEBUG) debug("Upgrade to version 23. Add type information to receivers and to");
+ self.upgradeSchema22(event.target.transaction, next);
break;
default:
event.target.transaction.abort();
@@ -701,8 +705,8 @@ MobileMessageDB.prototype = {
// retrieve the records out and insert its "senderOrReceiver" column as a
// new record in participantStore.
let number = mostRecentRecord.senderOrReceiver;
- self.findParticipantRecordByAddress(participantStore, number, true,
- function(participantRecord) {
+ self.findParticipantRecordByPlmnAddress(participantStore, number, true,
+ function(participantRecord) {
// Also create a new record in threadStore.
let threadRecord = {
participantIds: [participantRecord.id],
@@ -1072,9 +1076,9 @@ MobileMessageDB.prototype = {
threadParticipants = messageRecord.receivers;
}
}
- self.findThreadRecordByParticipants(threadStore, participantStore,
- threadParticipants, true,
- function(threadRecord,
+ self.findThreadRecordByPlmnAddresses(threadStore, participantStore,
+ threadParticipants, true,
+ function(threadRecord,
participantIds) {
if (!participantIds) {
debug("participantIds is empty!");
@@ -1408,6 +1412,209 @@ MobileMessageDB.prototype = {
next();
},
+ /**
+ * Change receivers format to address and type.
+ */
+ upgradeSchema22: function(transaction, next) {
+ // Since bug 871433 (DB_VERSION 11), we normalize addresses before really
+ // diving into participant store in findParticipantRecordByPlmnAddress.
+ // This also follows that all addresses stored in participant store are
+ // normalized phone numbers, although they might not be phone numbers at the
+ // first place. So addresses in participant store are not reliable.
+ //
+ // |participantAddresses| in a thread record are reliable, but several
+ // distinct threads can be wrongly mapped into one. For example, an IPv4
+ // address "55.252.255.54" was normalized as US phone number "5525225554".
+ // So beginning with thread store is not really a good idea.
+ //
+ // The only correct way is to begin with all messages records and check if
+ // the findThreadRecordByTypedAddresses() call using a message record's
+ // thread participants returns the same thread record with the one it
+ // currently belong to.
+
+ function getThreadParticipantsFromMessageRecord(aMessageRecord) {
+ let threadParticipants;
+
+ if (aMessageRecord.type == "sms") {
+ let address;
+ if (aMessageRecord.delivery == DELIVERY_RECEIVED) {
+ address = aMessageRecord.sender;
+ } else {
+ address = aMessageRecord.receiver;
+ }
+ threadParticipants = [{
+ address: address,
+ type: MMS.Address.resolveType(address)
+ }];
+ } else { // MMS
+ if ((aMessageRecord.delivery == DELIVERY_RECEIVED) ||
+ (aMessageRecord.delivery == DELIVERY_NOT_DOWNLOADED)) {
+ // DISABLE_MMS_GROUPING_FOR_RECEIVING is set to true at the time, so
+ // we consider only |aMessageRecord.sender|.
+ threadParticipants = [{
+ address: aMessageRecord.sender,
+ type: MMS.Address.resolveType(aMessageRecord.sender)
+ }];
+ } else {
+ threadParticipants = aMessageRecord.headers.to;
+ }
+ }
+
+ return threadParticipants;
+ }
+
+ let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME);
+ let threadStore = transaction.objectStore(THREAD_STORE_NAME);
+ let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
+
+ let invalidThreadIds = [];
+
+ let self = this;
+ let messageCursorReq = messageStore.openCursor();
+ messageCursorReq.onsuccess = function(aEvent) {
+ let messageCursor = aEvent.target.result;
+ if (messageCursor) {
+ let messageRecord = messageCursor.value;
+ let threadParticipants =
+ getThreadParticipantsFromMessageRecord(messageRecord);
+
+ // 1. If thread ID of this message record has been marked as invalid,
+ // skip further checks and go ahead for the next one.
+ if (invalidThreadIds.indexOf(messageRecord.threadId) >= 0) {
+ messageCursor.continue();
+ return;
+ }
+
+ // 2. Check if the thread record found with the new algorithm matches
+ // the original one.
+ self.findThreadRecordByTypedAddresses(threadStore, participantStore,
+ threadParticipants, true,
+ function(aThreadRecord,
+ aParticipantIds) {
+ if (!aThreadRecord || aThreadRecord.id !== messageRecord.threadId) {
+ invalidThreadIds.push(messageRecord.threadId);
+ }
+
+ messageCursor.continue();
+ });
+
+ // Only calls |messageCursor.continue()| inside the callback of
+ // findThreadRecordByTypedAddresses() because that may inserts new
+ // participant records and hurt concurrency.
+ return;
+ } // End of |if (messageCursor)|.
+
+ // 3. If there is no any mis-grouped message found, go on to next upgrade.
+ if (!invalidThreadIds.length) {
+ next();
+ return;
+ }
+
+ // 4. Remove invalid thread records first, so that we don't have
+ // unexpected match in findThreadRecordByTypedAddresses().
+ invalidThreadIds.forEach(function(aInvalidThreadId) {
+ threadStore.delete(aInvalidThreadId);
+ });
+
+ // 5. For each affected thread, re-create a valid thread record for it.
+ (function redoThreading(aInvalidThreadId) {
+ // 5-1. For each message record originally belongs to this thread, find
+ // a new home for it.
+ let range = IDBKeyRange.bound([aInvalidThreadId, 0],
+ [aInvalidThreadId, ""]);
+ let threadMessageCursorReq = messageStore.index("threadId")
+ .openCursor(range, NEXT);
+ threadMessageCursorReq.onsuccess = function(aEvent) {
+ let messageCursor = aEvent.target.result;
+
+ // 5-2. If no more message records to process in this invalid thread,
+ // go on to next invalid thread if available, or pass to next
+ // upgradeSchema function.
+ if (!messageCursor) {
+ if (invalidThreadIds.length) {
+ redoThreading(invalidThreadIds.shift());
+ } else {
+ next();
+ }
+ return;
+ }
+
+ let messageRecord = messageCursor.value;
+ let threadParticipants =
+ getThreadParticipantsFromMessageRecord(messageRecord);
+
+ // 5-3. Assign a thread record for this message record. Basically
+ // copied from |realSaveRecord|, but we don't have to worry
+ // about |updateThreadByMessageChange| because we've removed
+ // affected threads.
+ self.findThreadRecordByTypedAddresses(threadStore, participantStore,
+ threadParticipants, true,
+ function(aThreadRecord,
+ aParticipantIds) {
+ // Setup participantIdsIndex.
+ messageRecord.participantIdsIndex =
+ aParticipantIds.map(function(aParticipantId) {
+ return [aParticipantId, messageRecord.timestamp];
+ });
+
+ let threadExists = aThreadRecord ? true : false;
+ if (!threadExists) {
+ aThreadRecord = {
+ participantIds: aParticipantIds,
+ participantAddresses:
+ threadParticipants.map(function(aTypedAddress) {
+ return aTypedAddress.address;
+ }),
+ unreadCount: 0,
+ lastTimestamp: -1
+ };
+ }
+
+ let needsUpdate = false;
+ if (aThreadRecord.lastTimestamp <= messageRecord.timestamp) {
+ let lastMessageSubject;
+ if (messageRecord.type == "mms") {
+ lastMessageSubject = messageRecord.headers.subject;
+ }
+ aThreadRecord.lastMessageSubject = lastMessageSubject || null;
+ aThreadRecord.lastTimestamp = messageRecord.timestamp;
+ aThreadRecord.body = messageRecord.body;
+ aThreadRecord.lastMessageId = messageRecord.id;
+ aThreadRecord.lastMessageType = messageRecord.type;
+ needsUpdate = true;
+ }
+
+ if (!messageRecord.read) {
+ aThreadRecord.unreadCount++;
+ needsUpdate = true;
+ }
+
+ let updateMessageRecordThreadId = function(aThreadId) {
+ // Setup threadId & threadIdIndex.
+ messageRecord.threadId = aThreadId;
+ messageRecord.threadIdIndex = [aThreadId, messageRecord.timestamp];
+
+ messageCursor.update(messageRecord);
+ messageCursor.continue();
+ };
+
+ if (threadExists) {
+ if (needsUpdate) {
+ threadStore.put(aThreadRecord);
+ }
+ updateMessageRecordThreadId(aThreadRecord.id);
+ } else {
+ threadStore.add(aThreadRecord).onsuccess = function(aEvent) {
+ let threadId = aEvent.target.result;
+ updateMessageRecordThreadId(threadId);
+ };
+ }
+ }); // End of findThreadRecordByTypedAddresses().
+ }; // End of threadMessageCursorReq.onsuccess.
+ })(invalidThreadIds.shift()); // End of function redoThreading.
+ }; // End of messageStore.openCursor().onsuccess
+ },
+
matchParsedPhoneNumbers: function(addr1, parsedAddr1, addr2, parsedAddr2) {
if ((parsedAddr1.internationalNumber &&
parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) ||
@@ -1536,10 +1743,22 @@ MobileMessageDB.prototype = {
}
},
- findParticipantRecordByAddress: function(aParticipantStore, aAddress,
- aCreate, aCallback) {
+ createParticipantRecord: function(aParticipantStore, aAddresses, aCallback) {
+ let participantRecord = { addresses: aAddresses };
+ let addRequest = aParticipantStore.add(participantRecord);
+ addRequest.onsuccess = function(event) {
+ participantRecord.id = event.target.result;
+ if (DEBUG) {
+ debug("createParticipantRecord: " + JSON.stringify(participantRecord));
+ }
+ aCallback(participantRecord);
+ };
+ },
+
+ findParticipantRecordByPlmnAddress: function(aParticipantStore, aAddress,
+ aCreate, aCallback) {
if (DEBUG) {
- debug("findParticipantRecordByAddress("
+ debug("findParticipantRecordByPlmnAddress("
+ JSON.stringify(aAddress) + ", " + aCreate + ")");
}
@@ -1559,7 +1778,7 @@ MobileMessageDB.prototype = {
allPossibleAddresses.push(parsedAddress.internationalNumber);
}
if (DEBUG) {
- debug("findParticipantRecordByAddress: allPossibleAddresses = " +
+ debug("findParticipantRecordByPlmnAddress: allPossibleAddresses = " +
JSON.stringify(allPossibleAddresses));
}
@@ -1572,7 +1791,7 @@ MobileMessageDB.prototype = {
// If we're lucky, return the fetched participant record.
if (participantRecord) {
if (DEBUG) {
- debug("findParticipantRecordByAddress: got "
+ debug("findParticipantRecordByPlmnAddress: got "
+ JSON.stringify(participantRecord));
}
aCallback(participantRecord);
@@ -1596,16 +1815,8 @@ MobileMessageDB.prototype = {
return;
}
- let participantRecord = { addresses: [normalizedAddress] };
- let addRequest = aParticipantStore.add(participantRecord);
- addRequest.onsuccess = function(event) {
- participantRecord.id = event.target.result;
- if (DEBUG) {
- debug("findParticipantRecordByAddress: created "
- + JSON.stringify(participantRecord));
- }
- aCallback(participantRecord);
- };
+ this.createParticipantRecord(aParticipantStore, [normalizedAddress],
+ aCallback);
return;
}
@@ -1628,7 +1839,7 @@ MobileMessageDB.prototype = {
}
if (DEBUG) {
- debug("findParticipantRecordByAddress: match "
+ debug("findParticipantRecordByPlmnAddress: match "
+ JSON.stringify(cursor.value));
}
aCallback(participantRecord);
@@ -1641,16 +1852,58 @@ MobileMessageDB.prototype = {
}).bind(this);
},
- findParticipantIdsByAddresses: function(aParticipantStore, aAddresses,
- aCreate, aSkipNonexistent, aCallback) {
+ findParticipantRecordByOtherAddress: function(aParticipantStore, aAddress,
+ aCreate, aCallback) {
if (DEBUG) {
- debug("findParticipantIdsByAddresses("
+ debug("findParticipantRecordByOtherAddress(" +
+ JSON.stringify(aAddress) + ", " + aCreate + ")");
+ }
+
+ // Go full match.
+ let request = aParticipantStore.index("addresses").get(aAddress);
+ request.onsuccess = (function(event) {
+ let participantRecord = event.target.result;
+ if (participantRecord) {
+ if (DEBUG) {
+ debug("findParticipantRecordByOtherAddress: got "
+ + JSON.stringify(participantRecord));
+ }
+ aCallback(participantRecord);
+ return;
+ }
+ if (aCreate) {
+ this.createParticipantRecord(aParticipantStore, [aAddress], aCallback);
+ return;
+ }
+ aCallback(null);
+ }).bind(this);
+ },
+
+ findParticipantRecordByTypedAddress: function(aParticipantStore,
+ aTypedAddress, aCreate,
+ aCallback) {
+ if (aTypedAddress.type == "PLMN") {
+ this.findParticipantRecordByPlmnAddress(aParticipantStore,
+ aTypedAddress.address, aCreate,
+ aCallback);
+ } else {
+ this.findParticipantRecordByOtherAddress(aParticipantStore,
+ aTypedAddress.address, aCreate,
+ aCallback);
+ }
+ },
+
+ // For upgradeSchema13 usage.
+ findParticipantIdsByPlmnAddresses: function(aParticipantStore, aAddresses,
+ aCreate, aSkipNonexistent, aCallback) {
+ if (DEBUG) {
+ debug("findParticipantIdsByPlmnAddresses("
+ JSON.stringify(aAddresses) + ", "
+ aCreate + ", " + aSkipNonexistent + ")");
}
if (!aAddresses || !aAddresses.length) {
- if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
+ if (DEBUG) debug("findParticipantIdsByPlmnAddresses: returning null");
aCallback(null);
return;
}
@@ -1662,17 +1915,17 @@ MobileMessageDB.prototype = {
result.sort(function(a, b) {
return a - b;
});
- if (DEBUG) debug("findParticipantIdsByAddresses: returning " + result);
+ if (DEBUG) debug("findParticipantIdsByPlmnAddresses: returning " + result);
aCallback(result);
return;
}
- self.findParticipantRecordByAddress(aParticipantStore,
- aAddresses[index++], aCreate,
- function(participantRecord) {
+ self.findParticipantRecordByPlmnAddress(aParticipantStore,
+ aAddresses[index++], aCreate,
+ function(participantRecord) {
if (!participantRecord) {
if (!aSkipNonexistent) {
- if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
+ if (DEBUG) debug("findParticipantIdsByPlmnAddresses: returning null");
aCallback(null);
return;
}
@@ -1684,18 +1937,68 @@ MobileMessageDB.prototype = {
}) (0, []);
},
- findThreadRecordByParticipants: function(aThreadStore, aParticipantStore,
- aAddresses, aCreateParticipants,
- aCallback) {
+ findParticipantIdsByTypedAddresses: function(aParticipantStore,
+ aTypedAddresses, aCreate,
+ aSkipNonexistent, aCallback) {
if (DEBUG) {
- debug("findThreadRecordByParticipants(" + JSON.stringify(aAddresses)
+ debug("findParticipantIdsByTypedAddresses(" +
+ JSON.stringify(aTypedAddresses) + ", " +
+ aCreate + ", " + aSkipNonexistent + ")");
+ }
+
+ if (!aTypedAddresses || !aTypedAddresses.length) {
+ if (DEBUG) debug("findParticipantIdsByTypedAddresses: returning null");
+ aCallback(null);
+ return;
+ }
+
+ let self = this;
+ (function findParticipantId(index, result) {
+ if (index >= aTypedAddresses.length) {
+ // Sort numerically.
+ result.sort(function(a, b) {
+ return a - b;
+ });
+ if (DEBUG) {
+ debug("findParticipantIdsByTypedAddresses: returning " + result);
+ }
+ aCallback(result);
+ return;
+ }
+
+ self.findParticipantRecordByTypedAddress(aParticipantStore,
+ aTypedAddresses[index++],
+ aCreate,
+ function(participantRecord) {
+ if (!participantRecord) {
+ if (!aSkipNonexistent) {
+ if (DEBUG) {
+ debug("findParticipantIdsByTypedAddresses: returning null");
+ }
+ aCallback(null);
+ return;
+ }
+ } else if (result.indexOf(participantRecord.id) < 0) {
+ result.push(participantRecord.id);
+ }
+ findParticipantId(index, result);
+ });
+ }) (0, []);
+ },
+
+ // For upgradeSchema13 usage.
+ findThreadRecordByPlmnAddresses: function(aThreadStore, aParticipantStore,
+ aAddresses, aCreateParticipants,
+ aCallback) {
+ if (DEBUG) {
+ debug("findThreadRecordByPlmnAddresses(" + JSON.stringify(aAddresses)
+ ", " + aCreateParticipants + ")");
}
- this.findParticipantIdsByAddresses(aParticipantStore, aAddresses,
- aCreateParticipants, false,
- function(participantIds) {
+ this.findParticipantIdsByPlmnAddresses(aParticipantStore, aAddresses,
+ aCreateParticipants, false,
+ function(participantIds) {
if (!participantIds) {
- if (DEBUG) debug("findThreadRecordByParticipants: returning null");
+ if (DEBUG) debug("findThreadRecordByPlmnAddresses: returning null");
aCallback(null, null);
return;
}
@@ -1704,7 +2007,7 @@ MobileMessageDB.prototype = {
request.onsuccess = function(event) {
let threadRecord = event.target.result;
if (DEBUG) {
- debug("findThreadRecordByParticipants: return "
+ debug("findThreadRecordByPlmnAddresses: return "
+ JSON.stringify(threadRecord));
}
aCallback(threadRecord, participantIds);
@@ -1712,6 +2015,34 @@ MobileMessageDB.prototype = {
});
},
+ findThreadRecordByTypedAddresses: function(aThreadStore, aParticipantStore,
+ aTypedAddresses,
+ aCreateParticipants, aCallback) {
+ if (DEBUG) {
+ debug("findThreadRecordByTypedAddresses(" +
+ JSON.stringify(aTypedAddresses) + ", " + aCreateParticipants + ")");
+ }
+ this.findParticipantIdsByTypedAddresses(aParticipantStore, aTypedAddresses,
+ aCreateParticipants, false,
+ function(participantIds) {
+ if (!participantIds) {
+ if (DEBUG) debug("findThreadRecordByTypedAddresses: returning null");
+ aCallback(null, null);
+ return;
+ }
+ // Find record from thread store.
+ let request = aThreadStore.index("participantIds").get(participantIds);
+ request.onsuccess = function(event) {
+ let threadRecord = event.target.result;
+ if (DEBUG) {
+ debug("findThreadRecordByTypedAddresses: return " +
+ JSON.stringify(threadRecord));
+ }
+ aCallback(threadRecord, participantIds);
+ };
+ });
+ },
+
newTxnWithCallback: function(aCallback, aFunc, aStoreNames) {
let self = this;
this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) {
@@ -1742,7 +2073,7 @@ MobileMessageDB.prototype = {
}, aStoreNames);
},
- saveRecord: function(aMessageRecord, aAddresses, aCallback) {
+ saveRecord: function(aMessageRecord, aThreadParticipants, aCallback) {
if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
let self = this;
@@ -1776,13 +2107,14 @@ MobileMessageDB.prototype = {
let participantStore = stores[1];
let threadStore = stores[2];
self.replaceShortMessageOnSave(txn, messageStore, participantStore,
- threadStore, aMessageRecord, aAddresses);
+ threadStore, aMessageRecord,
+ aThreadParticipants);
}, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
},
replaceShortMessageOnSave: function(aTransaction, aMessageStore,
aParticipantStore, aThreadStore,
- aMessageRecord, aAddresses) {
+ aMessageRecord, aThreadParticipants) {
let isReplaceTypePid = (aMessageRecord.pid) &&
((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
@@ -1792,7 +2124,7 @@ MobileMessageDB.prototype = {
aMessageRecord.delivery != DELIVERY_RECEIVED ||
!isReplaceTypePid) {
this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
- aThreadStore, aMessageRecord, aAddresses);
+ aThreadStore, aMessageRecord, aThreadParticipants);
return;
}
@@ -1805,12 +2137,16 @@ MobileMessageDB.prototype = {
// shall store the message in the normal way. ... it is recommended
// that the SC address should not be checked by the MS."
let self = this;
- this.findParticipantRecordByAddress(aParticipantStore,
- aMessageRecord.sender, false,
- function(participantRecord) {
+ let typedSender = {
+ address: aMessageRecord.sender,
+ type: MMS.Address.resolveType(aMessageRecord.sender)
+ };
+ this.findParticipantRecordByTypedAddress(aParticipantStore, typedSender,
+ false,
+ function(participantRecord) {
if (!participantRecord) {
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
- aThreadStore, aMessageRecord, aAddresses);
+ aThreadStore, aMessageRecord, aThreadParticipants);
return;
}
@@ -1821,7 +2157,7 @@ MobileMessageDB.prototype = {
let cursor = event.target.result;
if (!cursor) {
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
- aThreadStore, aMessageRecord, aAddresses);
+ aThreadStore, aMessageRecord, aThreadParticipants);
return;
}
@@ -1838,17 +2174,18 @@ MobileMessageDB.prototype = {
// Match! Now replace that found message record with current one.
aMessageRecord.id = foundMessageRecord.id;
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
- aThreadStore, aMessageRecord, aAddresses);
+ aThreadStore, aMessageRecord, aThreadParticipants);
};
});
},
realSaveRecord: function(aTransaction, aMessageStore, aParticipantStore,
- aThreadStore, aMessageRecord, aAddresses) {
+ aThreadStore, aMessageRecord, aThreadParticipants) {
let self = this;
- this.findThreadRecordByParticipants(aThreadStore, aParticipantStore,
- aAddresses, true,
- function(threadRecord, participantIds) {
+ this.findThreadRecordByTypedAddresses(aThreadStore, aParticipantStore,
+ aThreadParticipants, true,
+ function(threadRecord,
+ participantIds) {
if (!participantIds) {
aTransaction.abort();
return;
@@ -1930,7 +2267,9 @@ MobileMessageDB.prototype = {
threadRecord = {
participantIds: participantIds,
- participantAddresses: aAddresses,
+ participantAddresses: aThreadParticipants.map(function(typedAddress) {
+ return typedAddress.address;
+ }),
lastMessageId: aMessageRecord.id,
lastTimestamp: timestamp,
lastMessageSubject: lastMessageSubject || null,
@@ -2127,7 +2466,13 @@ MobileMessageDB.prototype = {
if (DEBUG) debug("Error! Cannot strip out user's own phone number!");
}
- threadParticipants = threadParticipants.concat(slicedReceivers);
+ threadParticipants =
+ threadParticipants.concat(slicedReceivers).map(function(aAddress) {
+ return {
+ address: aAddress,
+ type: MMS.Address.resolveType(aAddress)
+ };
+ });
},
updateThreadByMessageChange: function(messageStore, threadStore, threadId,
@@ -2207,13 +2552,19 @@ MobileMessageDB.prototype = {
if (aMessage.headers.from) {
aMessage.sender = aMessage.headers.from.address;
} else {
- aMessage.sender = "anonymous";
+ aMessage.sender = "";
}
- threadParticipants = [aMessage.sender];
+ threadParticipants = [{
+ address: aMessage.sender,
+ type: MMS.Address.resolveType(aMessage.sender)
+ }];
this.fillReceivedMmsThreadParticipants(aMessage, threadParticipants);
} else { // SMS
- threadParticipants = [aMessage.sender];
+ threadParticipants = [{
+ address: aMessage.sender,
+ type: MMS.Address.resolveType(aMessage.sender)
+ }];
}
let timestamp = aMessage.timestamp;
@@ -2284,16 +2635,7 @@ MobileMessageDB.prototype = {
aMessage.deliveryTimestamp = 0;
}
} else if (aMessage.type == "mms") {
- let receivers = aMessage.receivers
- if (!Array.isArray(receivers)) {
- if (DEBUG) {
- debug("Need receivers for MMS. Fail to save the sending message.");
- }
- if (aCallback) {
- aCallback.notify(Cr.NS_ERROR_FAILURE, null);
- }
- return;
- }
+ let receivers = aMessage.receivers;
let readStatus = aMessage.headers["x-mms-read-report"]
? MMS.DOM_READ_STATUS_PENDING
: MMS.DOM_READ_STATUS_NOT_APPLICABLE;
@@ -2322,13 +2664,16 @@ MobileMessageDB.prototype = {
// |sentTimestamp| is not available when the message is still sedning.
aMessage.sentTimestamp = 0;
- let addresses;
+ let threadParticipants;
if (aMessage.type == "sms") {
- addresses = [aMessage.receiver];
+ threadParticipants = [{
+ address: aMessage.receiver,
+ type :MMS.Address.resolveType(aMessage.receiver)
+ }];
} else if (aMessage.type == "mms") {
- addresses = aMessage.receivers;
+ threadParticipants = aMessage.headers.to;
}
- this.saveRecord(aMessage, addresses, aCallback);
+ this.saveRecord(aMessage, threadParticipants, aCallback);
},
setMessageDeliveryByMessageId: function(messageId, receiver, delivery,
@@ -3011,9 +3356,15 @@ let FilterSearcherHelper = {
}
let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME);
- mmdb.findParticipantIdsByAddresses(participantStore, filter.numbers,
- false, true,
- (function(participantIds) {
+ let typedAddresses = filter.numbers.map(function(number) {
+ return {
+ address: number,
+ type: MMS.Address.resolveType(number)
+ };
+ });
+ mmdb.findParticipantIdsByTypedAddresses(participantStore, typedAddresses,
+ false, true,
+ (function(participantIds) {
if (!participantIds || !participantIds.length) {
// Oops! No such participant at all.
diff --git a/dom/mobilemessage/tests/marionette/manifest.ini b/dom/mobilemessage/tests/marionette/manifest.ini
index 15e50d2ed8a..e7f3dc38056 100644
--- a/dom/mobilemessage/tests/marionette/manifest.ini
+++ b/dom/mobilemessage/tests/marionette/manifest.ini
@@ -43,6 +43,8 @@ qemu = true
[test_mmdb_setmessagedeliverybyid_sms.js]
[test_mmdb_foreachmatchedmmsdeliveryinfo.js]
[test_mmdb_full_storage.js]
+[test_mmdb_upgradeSchema_current_structure.js]
+[test_mmdb_upgradeSchema_22.js]
[test_replace_short_message_type.js]
[test_mt_sms_concatenation.js]
[test_error_of_mms_manual_retrieval.js]
diff --git a/dom/mobilemessage/tests/marionette/test_filter_number.js b/dom/mobilemessage/tests/marionette/test_filter_number.js
index c71a8c3b11a..9d37dc5536e 100644
--- a/dom/mobilemessage/tests/marionette/test_filter_number.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_number.js
@@ -2,200 +2,100 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
-const NUM_THREADS = 10;
-const REMOTE_NATIONAL_NUMBER = "555531555";
+const REMOTE_NATIONAL_NUMBER = "552522555";
const REMOTE_INTERNATIONAL_NUMBER = "+1" + REMOTE_NATIONAL_NUMBER;
-SpecialPowers.addPermission("sms", true, document);
-SpecialPowers.setBoolPref("dom.sms.enabled", true);
+const TEXT_1 = "Nice to meet you";
+const TEXT_2 = "Nice to meet you, too";
-let pendingEmulatorCmdCount = 0;
-function sendSmsToEmulator(from, text) {
- ++pendingEmulatorCmdCount;
-
- let cmd = "sms send " + from + " " + text;
- runEmulatorCmd(cmd, function(result) {
- --pendingEmulatorCmdCount;
-
- is(result[0], "OK", "Emulator response");
- });
+// Have a long long subject causes the send fails, so we don't need
+// networking here.
+const MMS_MAX_LENGTH_SUBJECT = 40;
+function genMmsSubject(sep) {
+ return "Hello " + (new Array(MMS_MAX_LENGTH_SUBJECT).join(sep)) + " World!";
}
-let tasks = {
- // List of test fuctions. Each of them should call |tasks.next()| when
- // completed or |tasks.finish()| to jump to the last one.
- _tasks: [],
- _nextTaskIndex: 0,
-
- push: function(func) {
- this._tasks.push(func);
- },
-
- next: function() {
- let index = this._nextTaskIndex++;
- let task = this._tasks[index];
- try {
- task();
- } catch (ex) {
- ok(false, "test task[" + index + "] throws: " + ex);
- // Run last task as clean up if possible.
- if (index != this._tasks.length - 1) {
- this.finish();
- }
- }
- },
-
- finish: function() {
- this._tasks[this._tasks.length - 1]();
- },
-
- run: function() {
- this.next();
- }
-};
-
-let manager;
-function getAllMessages(callback, filter, reverse) {
- if (!filter) {
- filter = new MozSmsFilter;
- }
- let messages = [];
- let request = manager.getMessages(filter, reverse || false);
- request.onsuccess = function(event) {
- if (request.result) {
- messages.push(request.result);
- request.continue();
- return;
- }
-
- window.setTimeout(callback.bind(null, messages), 0);
- }
+function genFailingMms(aReceivers) {
+ return {
+ receivers: aReceivers,
+ subject: genMmsSubject(' '),
+ attachments: [],
+ };
}
-function deleteAllMessages() {
- log("Deleting all messages.");
- getAllMessages(function deleteAll(messages) {
- let message = messages.shift();
- if (!message) {
- ok(true, "all messages deleted");
- tasks.next();
- return;
- }
-
- let request = manager.delete(message.id);
- request.onsuccess = deleteAll.bind(null, messages);
- request.onerror = function(event) {
- ok(false, "failed to delete all messages");
- tasks.finish();
- }
- });
-}
-
-function checkMessage(needle, secondary) {
- log(" Verifying " + needle);
+function checkMessage(aNeedle, aValidNumbers) {
+ log(" Verifying " + aNeedle);
let filter = new MozSmsFilter();
- filter.numbers = [needle];
- getAllMessages(function(messages) {
- is(messages.length, 2, "should have exactly 2 messages");
+ filter.numbers = [aNeedle];
+ return getMessages(filter)
+ .then(function(messages) {
+ // Check the messages are sent to/received from aValidNumbers.
+ is(messages.length, aValidNumbers.length, "messages.length");
- // Check the messages are sent to/received from either 'needle' or
- // 'secondary' number.
- let validNumbers = [needle, secondary];
- for (let message of messages) {
- let number = (message.delivery === "received") ? message.sender
+ for (let message of messages) {
+ let number;
+ if (message.type == "sms") {
+ number = (message.delivery === "received") ? message.sender
: message.receiver;
- let index = validNumbers.indexOf(number);
- ok(index >= 0, "message.number");
- validNumbers.splice(index, 1); // Remove from validNumbers.
- }
+ } else {
+ number = message.receivers[0];
+ }
- tasks.next();
- }, filter);
+ let index = aValidNumbers.indexOf(number);
+ ok(index >= 0, "message.number");
+ aValidNumbers.splice(index, 1); // Remove from aValidNumbers.
+ }
+
+ is(aValidNumbers.length, 0, "aValidNumbers.length");
+ });
}
-tasks.push(function verifyInitialState() {
- log("Verifying initial state.");
- manager = window.navigator.mozMobileMessage;
- ok(manager instanceof MozMobileMessageManager,
- "manager is instance of " + manager.constructor);
- tasks.next();
+startTestCommon(function testCaseMain() {
+ return Promise.resolve()
+
+ // SMS, MO:international, MT:national
+ .then(() => sendSmsWithSuccess(REMOTE_INTERNATIONAL_NUMBER + '1', TEXT_1))
+ .then(() => sendTextSmsToEmulator(REMOTE_NATIONAL_NUMBER + '1', TEXT_2))
+ // SMS, MO:national, MT:international
+ .then(() => sendSmsWithSuccess(REMOTE_NATIONAL_NUMBER + '2', TEXT_1))
+ .then(() => sendTextSmsToEmulator(REMOTE_INTERNATIONAL_NUMBER + '2', TEXT_2))
+ // MMS, international
+ .then(() => sendMmsWithFailure(genFailingMms([REMOTE_INTERNATIONAL_NUMBER + '3'])))
+ // MMS, national
+ .then(() => sendMmsWithFailure(genFailingMms([REMOTE_NATIONAL_NUMBER + '3'])))
+ // SMS, MO:international, MT:national, ambiguous number.
+ .then(() => sendSmsWithSuccess(REMOTE_INTERNATIONAL_NUMBER + '4', TEXT_1))
+ .then(() => sendTextSmsToEmulator(REMOTE_NATIONAL_NUMBER + '4', TEXT_2))
+ .then(() => sendMmsWithFailure(genFailingMms(["jkalbcjklg"])))
+ .then(() => sendMmsWithFailure(genFailingMms(["jk.alb.cjk.lg"])))
+ // MMS, email
+ .then(() => sendMmsWithFailure(genFailingMms(["jk@alb.cjk.lg"])))
+ // MMS, IPv4
+ .then(() => sendMmsWithFailure(genFailingMms(["55.252.255.54"])))
+ // MMS, IPv6
+ .then(() => sendMmsWithFailure(genFailingMms(["5:5:2:5:2:2:55:54"])))
+
+ .then(() => checkMessage(REMOTE_INTERNATIONAL_NUMBER + '1',
+ [ REMOTE_INTERNATIONAL_NUMBER + '1',
+ REMOTE_NATIONAL_NUMBER + '1' ]))
+ .then(() => checkMessage(REMOTE_NATIONAL_NUMBER + '2',
+ [ REMOTE_NATIONAL_NUMBER + '2',
+ REMOTE_INTERNATIONAL_NUMBER + '2' ]))
+ .then(() => checkMessage(REMOTE_INTERNATIONAL_NUMBER + '3',
+ [ REMOTE_INTERNATIONAL_NUMBER + '3',
+ REMOTE_NATIONAL_NUMBER + '3' ]))
+ .then(() => checkMessage(REMOTE_NATIONAL_NUMBER + '3',
+ [ REMOTE_NATIONAL_NUMBER + '3',
+ REMOTE_INTERNATIONAL_NUMBER + '3' ]))
+ .then(() => checkMessage(REMOTE_NATIONAL_NUMBER + '4',
+ [ REMOTE_NATIONAL_NUMBER + '4',
+ REMOTE_INTERNATIONAL_NUMBER + '4',
+ "jkalbcjklg",
+ "jk.alb.cjk.lg" ]))
+ .then(() => checkMessage("jk@alb.cjk.lg", ["jk@alb.cjk.lg"]))
+ .then(() => checkMessage("55.252.255.54", ["55.252.255.54"]))
+ .then(() => checkMessage("5:5:2:5:2:2:55:54", ["5:5:2:5:2:2:55:54"]))
});
-
-tasks.push(deleteAllMessages);
-
-/**
- * Populate database with messages to being tests. We'll have NUM_THREADS
- * sent and received messages.
- *
- * send to "+15555315550"
- * receive from "5555315550", count = 1
- *
- * send to "+15555315551"
- * receive from "5555315551", count = 2
- * ...
- * send to "+15555315559"
- * receive from "5555315559", count = 10
- */
-tasks.push(function populateMessages() {
- log("Populating messages.");
- let count = 0;
-
- function sendMessage(iter) {
- let request = manager.send(REMOTE_INTERNATIONAL_NUMBER + iter,
- "Nice to meet you");
- request.onsuccess = function onRequestSuccess(event) {
- sendSmsToEmulator(REMOTE_NATIONAL_NUMBER + iter,
- "Nice to meet you, too");
- }
- request.onerror = function onRequestError(event) {
- tasks.finish();
- }
- }
-
- manager.addEventListener("received", function onReceived(event) {
- ++count;
- if (count < NUM_THREADS) {
- sendMessage(count);
- } else {
- manager.removeEventListener("received", onReceived);
- tasks.next();
- }
- });
-
- sendMessage(count);
-});
-
-tasks.push(function() {
- log("Verifying number of messages in database");
- getAllMessages(function(messages) {
- is(messages.length, NUM_THREADS * 2,
- "should have exactly " + (NUM_THREADS * 2) + " messages");
-
- tasks.next();
- });
-});
-
-for (let iter = 0; iter < NUM_THREADS; iter++) {
- let national = REMOTE_NATIONAL_NUMBER + iter;
- let international = REMOTE_INTERNATIONAL_NUMBER + iter;
- tasks.push(checkMessage.bind(null, national, international));
- tasks.push(checkMessage.bind(null, international, national));
-}
-
-tasks.push(deleteAllMessages);
-
-// WARNING: All tasks should be pushed before this!!!
-tasks.push(function cleanUp() {
- if (pendingEmulatorCmdCount) {
- window.setTimeout(cleanUp, 100);
- return;
- }
-
- SpecialPowers.removePermission("sms", document);
- SpecialPowers.clearUserPref("dom.sms.enabled");
- finish();
-});
-
-tasks.run();
diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js b/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js
index 75e585b3ac7..c11633b5eda 100644
--- a/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js
@@ -2,14 +2,15 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
+MARIONETTE_HEAD_JS = 'mmdb_head.js';
-const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
- "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
+const DBNAME = "test_mmdb_setmessagedeliverybyid_sms:" + newUUID();
const RECEIVER = "+1234567890";
const TEXT = "The quick brown fox jumps over the lazy dog.";
+const MESSAGE_STORE_NAME = "sms";
+
const DELIVERY_SENDING = "sending";
const DELIVERY_SENT = "sent";
const DELIVERY_RECEIVED = "received";
@@ -21,96 +22,113 @@ const DELIVERY_STATUS_SUCCESS = "success";
const DELIVERY_STATUS_PENDING = "pending";
const DELIVERY_STATUS_ERROR = "error";
-let dbService;
+function verify(aMmdb, aMessageId, aDelivery, aDeliveryStatus, aRv, aDomMessage) {
+ log(" Verify(" + aMessageId + ", " + aDelivery + ", " + aDeliveryStatus + ")");
-/**
- * @param aMessageId
- * @param aParams
- * An array of four elements [, ,
- * , ].
- */
-function setMessageDeliveryByMessageId(aMessageId, aParams) {
- if (!dbService) {
- dbService = Cc[RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID]
- .getService(Ci.nsIRilMobileMessageDatabaseService);
- if (!dbService) {
- log(" Failed to get database service.");
- return Promise.reject();
- }
- }
+ ok(Components.isSuccessCode(aRv), "Components.isSuccessCode(" + aRv + ")");
+ ok(aDomMessage, "DOM message validity");
+ is(aDomMessage.delivery, aDelivery, "message.delivery");
+ is(aDomMessage.deliveryStatus, aDeliveryStatus, "message.deliveryStatus");
let deferred = Promise.defer();
- log(" Set to " + aParams[0] + ":" + aParams[1]);
- dbService.setMessageDeliveryByMessageId(aMessageId, null, aParams[0],
- aParams[1], null,
- function(aRv, aDomMessage) {
- if (aRv !== Cr.NS_OK) {
- deferred.reject(aRv);
- return;
- }
+ // Verify deliveryIndex, sentTimestamp and deliveryTimestamp
+ aMmdb.newTxn("readonly", function(aError, aTransaction, aMessageStore) {
+ let messageRecord;
+ aTransaction.oncomplete = function() {
+ ok(true, "transaction complete");
- is(aDomMessage.delivery, aParams[2], "message.delivery");
- is(aDomMessage.deliveryStatus, aParams[3], "message.deliveryStatus");
+ is(messageRecord.deliveryIndex[0], aDelivery,
+ "messageRecord.deliveryIndex[0]");
+ is(messageRecord.deliveryIndex[1], messageRecord.timestamp,
+ "messageRecord.deliveryIndex[1]");
+ if (aDelivery == DELIVERY_SENT) {
+ ok(messageRecord.sentTimestamp >= messageRecord.timestamp,
+ "messageRecord.sentTimestamp");
+ }
+ if (aDeliveryStatus == DELIVERY_STATUS_SUCCESS) {
+ ok(messageRecord.deliveryTimestamp >= messageRecord.timestamp,
+ "messageRecord.deliveryTimestamp");
+ }
- deferred.resolve([aRv, aDomMessage]);
- });
+ deferred.resolve(aMmdb);
+ };
+
+ aMessageStore.get(aMessageId).onsuccess = function(event) {
+ messageRecord = event.target.result;
+ ok(true, "Got messageRecord " + messageRecord.id);
+ };
+ }, [MESSAGE_STORE_NAME]);
return deferred.promise;
}
-function test(aTitle, aParamArray) {
+function test(aTitle, aMmdb, aDeliveryStatusRequested, aParamArray) {
log(aTitle);
- return sendSmsWithSuccess(RECEIVER, TEXT)
- .then(function(aDomMessage) {
- let id = aDomMessage.id;
-
- let promise = Promise.resolve();
+ let message = {
+ type: "sms",
+ sender: null,
+ timestamp: Date.now(),
+ deliveryStatusRequested: aDeliveryStatusRequested,
+ receiver: RECEIVER,
+ iccId: null,
+ body: TEXT,
+ };
+ return saveSendingMessage(aMmdb, message)
+ .then(function(aValues) {
+ let [resultCode, domMessage] = aValues;
+ let promise =
+ verify(aMmdb, domMessage.id, DELIVERY_SENDING,
+ (aDeliveryStatusRequested ? DELIVERY_STATUS_PENDING
+ : DELIVERY_STATUS_NOT_APPLICABLE),
+ resultCode, domMessage);
while (aParamArray.length) {
let params = aParamArray.shift();
- promise =
- promise.then(setMessageDeliveryByMessageId.bind(null, id, params));
+ let v = verify.bind(null, aMmdb, domMessage.id, params[2], params[3]);
+ promise = promise
+ .then(() => {
+ log(" Set to " + params[0] + ":" + params[1]);
+ return setMessageDeliveryByMessageId(aMmdb, domMessage.id, null,
+ params[0], params[1], null);
+ })
+ .then((aValues) => v(aValues[0], aValues[1]));
}
return promise;
});
}
-startTestCommon(function testCaseMain() {
- return Promise.resolve()
- .then(test.bind(null, "Simulate send failed without delivery report requisition", [
- [DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE,
- DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE],
+startTestBase(function testCaseMain() {
+ return initMobileMessageDB(newMobileMessageDB(), DBNAME, 0)
+ .then((aMmdb) => test("Simulate send failed without delivery report requisition",
+ aMmdb, false, [
[DELIVERY_ERROR, DELIVERY_STATUS_ERROR,
DELIVERY_ERROR, DELIVERY_STATUS_ERROR],
]))
- .then(test.bind(null, "Simulate send failed with delivery report requisition", [
- [DELIVERY_SENDING, DELIVERY_STATUS_PENDING,
- DELIVERY_SENDING, DELIVERY_STATUS_PENDING],
+ .then((aMmdb) => test("Simulate send failed with delivery report requisition",
+ aMmdb, true, [
[DELIVERY_ERROR, DELIVERY_STATUS_ERROR,
DELIVERY_ERROR, DELIVERY_STATUS_ERROR],
]))
- .then(test.bind(null, "Simulate sent without delivery report requisition", [
- [DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE,
- DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE],
+ .then((aMmdb) => test("Simulate sent without delivery report requisition",
+ aMmdb, false, [
[DELIVERY_SENT, null,
DELIVERY_SENT, DELIVERY_STATUS_NOT_APPLICABLE],
]))
- .then(test.bind(null, "Simulate sent with delivery report success", [
- [DELIVERY_SENDING, DELIVERY_STATUS_PENDING,
- DELIVERY_SENDING, DELIVERY_STATUS_PENDING],
+ .then((aMmdb) => test("Simulate sent with delivery report success",
+ aMmdb, true, [
[DELIVERY_SENT, null,
DELIVERY_SENT, DELIVERY_STATUS_PENDING],
[null, DELIVERY_STATUS_SUCCESS,
DELIVERY_SENT, DELIVERY_STATUS_SUCCESS],
]))
- .then(test.bind(null, "Simulate sent with delivery report error", [
- [DELIVERY_SENDING, DELIVERY_STATUS_PENDING,
- DELIVERY_SENDING, DELIVERY_STATUS_PENDING],
+ .then((aMmdb) => test("Simulate sent with delivery report error",
+ aMmdb, true, [
[DELIVERY_SENT, null,
DELIVERY_SENT, DELIVERY_STATUS_PENDING],
[null, DELIVERY_ERROR,
DELIVERY_SENT, DELIVERY_ERROR],
- ]));
+ ]))
+ .then(closeMobileMessageDB);
});
diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
new file mode 100644
index 00000000000..e9582b6a1e4
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
@@ -0,0 +1,737 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'mmdb_head.js';
+
+Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
+
+let RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
+let MMS = {};
+Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
+
+const DBNAME = "test_mmdb_upgradeSchema_22:" + newUUID();
+
+const MESSAGE_STORE_NAME = "sms";
+const THREAD_STORE_NAME = "thread";
+const PARTICIPANT_STORE_NAME = "participant";
+
+const DEBUG = false;
+
+const READ_WRITE = "readwrite";
+
+const DELIVERY_SENDING = "sending";
+const DELIVERY_SENT = "sent";
+const DELIVERY_RECEIVED = "received";
+const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
+const DELIVERY_ERROR = "error";
+
+const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
+const DELIVERY_STATUS_SUCCESS = "success";
+const DELIVERY_STATUS_PENDING = "pending";
+const DELIVERY_STATUS_ERROR = "error";
+
+const MESSAGE_CLASS_NORMAL = "normal";
+
+const FILTER_READ_UNREAD = 0;
+const FILTER_READ_READ = 1;
+
+const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
+
+let LEGACY = {
+ saveRecord: function(aMessageRecord, aAddresses, aCallback) {
+ if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
+
+ let self = this;
+ this.newTxn(READ_WRITE, function(error, txn, stores) {
+ let notifyResult = function(aRv, aMessageRecord) {
+ if (!aCallback) {
+ return;
+ }
+ let domMessage =
+ aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
+ aCallback.notify(aRv, domMessage);
+ };
+
+ if (error) {
+ notifyResult(error, null);
+ return;
+ }
+
+ txn.oncomplete = function oncomplete(event) {
+ if (aMessageRecord.id > self.lastMessageId) {
+ self.lastMessageId = aMessageRecord.id;
+ }
+ notifyResult(Cr.NS_OK, aMessageRecord);
+ };
+ txn.onabort = function onabort(event) {
+ // TODO bug 832140 check event.target.errorCode
+ notifyResult(Cr.NS_ERROR_FAILURE, null);
+ };
+
+ let messageStore = stores[0];
+ let participantStore = stores[1];
+ let threadStore = stores[2];
+ LEGACY.replaceShortMessageOnSave.call(self, txn, messageStore,
+ participantStore, threadStore,
+ aMessageRecord, aAddresses);
+ }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
+ },
+
+ replaceShortMessageOnSave: function(aTransaction, aMessageStore,
+ aParticipantStore, aThreadStore,
+ aMessageRecord, aAddresses) {
+ let isReplaceTypePid = (aMessageRecord.pid) &&
+ ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
+ aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
+ aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE);
+
+ if (aMessageRecord.type != "sms" ||
+ aMessageRecord.delivery != DELIVERY_RECEIVED ||
+ !isReplaceTypePid) {
+ LEGACY.realSaveRecord.call(this, aTransaction, aMessageStore,
+ aParticipantStore, aThreadStore,
+ aMessageRecord, aAddresses);
+ return;
+ }
+
+ // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)":
+ //
+ // ... the MS shall check the originating address and replace any
+ // existing stored message having the same Protocol Identifier code
+ // and originating address with the new short message and other
+ // parameter values. If there is no message to be replaced, the MS
+ // shall store the message in the normal way. ... it is recommended
+ // that the SC address should not be checked by the MS."
+ let self = this;
+ this.findParticipantRecordByPlmnAddress(aParticipantStore,
+ aMessageRecord.sender, false,
+ function(participantRecord) {
+ if (!participantRecord) {
+ LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore,
+ aParticipantStore, aThreadStore,
+ aMessageRecord, aAddresses);
+ return;
+ }
+
+ let participantId = participantRecord.id;
+ let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]);
+ let request = aMessageStore.index("participantIds").openCursor(range);
+ request.onsuccess = function onsuccess(event) {
+ let cursor = event.target.result;
+ if (!cursor) {
+ LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore,
+ aParticipantStore, aThreadStore,
+ aMessageRecord, aAddresses);
+ return;
+ }
+
+ // A message record with same participantId found.
+ // Verify matching criteria.
+ let foundMessageRecord = cursor.value;
+ if (foundMessageRecord.type != "sms" ||
+ foundMessageRecord.sender != aMessageRecord.sender ||
+ foundMessageRecord.pid != aMessageRecord.pid) {
+ cursor.continue();
+ return;
+ }
+
+ // Match! Now replace that found message record with current one.
+ aMessageRecord.id = foundMessageRecord.id;
+ LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore,
+ aParticipantStore, aThreadStore,
+ aMessageRecord, aAddresses);
+ };
+ });
+ },
+
+ realSaveRecord: function(aTransaction, aMessageStore, aParticipantStore,
+ aThreadStore, aMessageRecord, aAddresses) {
+ let self = this;
+ this.findThreadRecordByPlmnAddresses(aThreadStore, aParticipantStore,
+ aAddresses, true,
+ function(threadRecord, participantIds) {
+ if (!participantIds) {
+ aTransaction.abort();
+ return;
+ }
+
+ let isOverriding = (aMessageRecord.id !== undefined);
+ if (!isOverriding) {
+ // |self.lastMessageId| is only updated in |txn.oncomplete|.
+ aMessageRecord.id = self.lastMessageId + 1;
+ }
+
+ let timestamp = aMessageRecord.timestamp;
+ let insertMessageRecord = function(threadId) {
+ // Setup threadId & threadIdIndex.
+ aMessageRecord.threadId = threadId;
+ aMessageRecord.threadIdIndex = [threadId, timestamp];
+ // Setup participantIdsIndex.
+ aMessageRecord.participantIdsIndex = [];
+ for each (let id in participantIds) {
+ aMessageRecord.participantIdsIndex.push([id, timestamp]);
+ }
+
+ if (!isOverriding) {
+ // Really add to message store.
+ aMessageStore.put(aMessageRecord);
+ return;
+ }
+
+ // If we're going to override an old message, we need to update the
+ // info of the original thread containing the overridden message.
+ // To get the original thread ID and read status of the overridden
+ // message record, we need to retrieve it before overriding it.
+ aMessageStore.get(aMessageRecord.id).onsuccess = function(event) {
+ let oldMessageRecord = event.target.result;
+ aMessageStore.put(aMessageRecord);
+ if (oldMessageRecord) {
+ self.updateThreadByMessageChange(aMessageStore,
+ aThreadStore,
+ oldMessageRecord.threadId,
+ aMessageRecord.id,
+ oldMessageRecord.read);
+ }
+ };
+ };
+
+ if (threadRecord) {
+ let needsUpdate = false;
+
+ if (threadRecord.lastTimestamp <= timestamp) {
+ let lastMessageSubject;
+ if (aMessageRecord.type == "mms") {
+ lastMessageSubject = aMessageRecord.headers.subject;
+ }
+ threadRecord.lastMessageSubject = lastMessageSubject || null;
+ threadRecord.lastTimestamp = timestamp;
+ threadRecord.body = aMessageRecord.body;
+ threadRecord.lastMessageId = aMessageRecord.id;
+ threadRecord.lastMessageType = aMessageRecord.type;
+ needsUpdate = true;
+ }
+
+ if (!aMessageRecord.read) {
+ threadRecord.unreadCount++;
+ needsUpdate = true;
+ }
+
+ if (needsUpdate) {
+ aThreadStore.put(threadRecord);
+ }
+
+ insertMessageRecord(threadRecord.id);
+ return;
+ }
+
+ let lastMessageSubject;
+ if (aMessageRecord.type == "mms") {
+ lastMessageSubject = aMessageRecord.headers.subject;
+ }
+
+ threadRecord = {
+ participantIds: participantIds,
+ participantAddresses: aAddresses,
+ lastMessageId: aMessageRecord.id,
+ lastTimestamp: timestamp,
+ lastMessageSubject: lastMessageSubject || null,
+ body: aMessageRecord.body,
+ unreadCount: aMessageRecord.read ? 0 : 1,
+ lastMessageType: aMessageRecord.type,
+ };
+ aThreadStore.add(threadRecord).onsuccess = function(event) {
+ let threadId = event.target.result;
+ insertMessageRecord(threadId);
+ };
+ });
+ },
+
+ fillReceivedMmsThreadParticipants: function(aMessage, threadParticipants) {
+ let receivers = aMessage.receivers;
+ // If we don't want to disable the MMS grouping for receiving, we need to
+ // add the receivers (excluding the user's own number) to the participants
+ // for creating the thread. Some cases might be investigated as below:
+ //
+ // 1. receivers.length == 0
+ // This usually happens when receiving an MMS notification indication
+ // which doesn't carry any receivers.
+ // 2. receivers.length == 1
+ // If the receivers contain single phone number, we don't need to
+ // add it into participants because we know that number is our own.
+ // 3. receivers.length >= 2
+ // If the receivers contain multiple phone numbers, we need to add all
+ // of them but not the user's own number into participants.
+ if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) {
+ return;
+ }
+ let isSuccess = false;
+ let slicedReceivers = receivers.slice();
+ if (aMessage.msisdn) {
+ let found = slicedReceivers.indexOf(aMessage.msisdn);
+ if (found !== -1) {
+ isSuccess = true;
+ slicedReceivers.splice(found, 1);
+ }
+ }
+
+ if (!isSuccess) {
+ // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's
+ // own phone number), so we cannot correcly exclude the user's own
+ // number from the receivers, thus wrongly building the thread index.
+ if (DEBUG) debug("Error! Cannot strip out user's own phone number!");
+ }
+
+ threadParticipants = threadParticipants.concat(slicedReceivers);
+ },
+
+ saveReceivedMessage: function(aMessage, aCallback) {
+ if ((aMessage.type != "sms" && aMessage.type != "mms") ||
+ (aMessage.type == "sms" && (aMessage.messageClass == undefined ||
+ aMessage.sender == undefined)) ||
+ (aMessage.type == "mms" && (aMessage.delivery == undefined ||
+ aMessage.deliveryStatus == undefined ||
+ !Array.isArray(aMessage.receivers))) ||
+ aMessage.timestamp == undefined) {
+ if (aCallback) {
+ aCallback.notify(Cr.NS_ERROR_FAILURE, null);
+ }
+ return;
+ }
+
+ let threadParticipants;
+ if (aMessage.type == "mms") {
+ if (aMessage.headers.from) {
+ aMessage.sender = aMessage.headers.from.address;
+ } else {
+ aMessage.sender = "anonymous";
+ }
+
+ threadParticipants = [aMessage.sender];
+ LEGACY.fillReceivedMmsThreadParticipants.call(this, aMessage,
+ threadParticipants);
+ } else { // SMS
+ threadParticipants = [aMessage.sender];
+ }
+
+ let timestamp = aMessage.timestamp;
+
+ // Adding needed indexes and extra attributes for internal use.
+ // threadIdIndex & participantIdsIndex are filled in saveRecord().
+ aMessage.readIndex = [FILTER_READ_UNREAD, timestamp];
+ aMessage.read = FILTER_READ_UNREAD;
+
+ // If |sentTimestamp| is not specified, use 0 as default.
+ if (aMessage.sentTimestamp == undefined) {
+ aMessage.sentTimestamp = 0;
+ }
+
+ if (aMessage.type == "mms") {
+ aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"];
+ aMessage.isReadReportSent = false;
+
+ // As a receiver, we don't need to care about the delivery status of
+ // others, so we put a single element with self's phone number in the
+ // |deliveryInfo| array.
+ aMessage.deliveryInfo = [{
+ receiver: aMessage.phoneNumber,
+ deliveryStatus: aMessage.deliveryStatus,
+ deliveryTimestamp: 0,
+ readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE,
+ readTimestamp: 0,
+ }];
+
+ delete aMessage.deliveryStatus;
+ }
+
+ if (aMessage.type == "sms") {
+ aMessage.delivery = DELIVERY_RECEIVED;
+ aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS;
+ aMessage.deliveryTimestamp = 0;
+
+ if (aMessage.pid == undefined) {
+ aMessage.pid = RIL.PDU_PID_DEFAULT;
+ }
+ }
+ aMessage.deliveryIndex = [aMessage.delivery, timestamp];
+
+ LEGACY.saveRecord.call(this, aMessage, threadParticipants, aCallback);
+ },
+
+ saveSendingMessage: function(aMessage, aCallback) {
+ if ((aMessage.type != "sms" && aMessage.type != "mms") ||
+ (aMessage.type == "sms" && aMessage.receiver == undefined) ||
+ (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) ||
+ aMessage.deliveryStatusRequested == undefined ||
+ aMessage.timestamp == undefined) {
+ if (aCallback) {
+ aCallback.notify(Cr.NS_ERROR_FAILURE, null);
+ }
+ return;
+ }
+
+ // Set |aMessage.deliveryStatus|. Note that for MMS record
+ // it must be an array of strings; For SMS, it's a string.
+ let deliveryStatus = aMessage.deliveryStatusRequested
+ ? DELIVERY_STATUS_PENDING
+ : DELIVERY_STATUS_NOT_APPLICABLE;
+ if (aMessage.type == "sms") {
+ aMessage.deliveryStatus = deliveryStatus;
+ // If |deliveryTimestamp| is not specified, use 0 as default.
+ if (aMessage.deliveryTimestamp == undefined) {
+ aMessage.deliveryTimestamp = 0;
+ }
+ } else if (aMessage.type == "mms") {
+ let receivers = aMessage.receivers
+ if (!Array.isArray(receivers)) {
+ if (DEBUG) {
+ debug("Need receivers for MMS. Fail to save the sending message.");
+ }
+ if (aCallback) {
+ aCallback.notify(Cr.NS_ERROR_FAILURE, null);
+ }
+ return;
+ }
+ let readStatus = aMessage.headers["x-mms-read-report"]
+ ? MMS.DOM_READ_STATUS_PENDING
+ : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
+ aMessage.deliveryInfo = [];
+ for (let i = 0; i < receivers.length; i++) {
+ aMessage.deliveryInfo.push({
+ receiver: receivers[i],
+ deliveryStatus: deliveryStatus,
+ deliveryTimestamp: 0,
+ readStatus: readStatus,
+ readTimestamp: 0,
+ });
+ }
+ }
+
+ let timestamp = aMessage.timestamp;
+
+ // Adding needed indexes and extra attributes for internal use.
+ // threadIdIndex & participantIdsIndex are filled in saveRecord().
+ aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp];
+ aMessage.readIndex = [FILTER_READ_READ, timestamp];
+ aMessage.delivery = DELIVERY_SENDING;
+ aMessage.messageClass = MESSAGE_CLASS_NORMAL;
+ aMessage.read = FILTER_READ_READ;
+
+ // |sentTimestamp| is not available when the message is still sedning.
+ aMessage.sentTimestamp = 0;
+
+ let addresses;
+ if (aMessage.type == "sms") {
+ addresses = [aMessage.receiver];
+ } else if (aMessage.type == "mms") {
+ addresses = aMessage.receivers;
+ }
+ LEGACY.saveRecord.call(this, aMessage, addresses, aCallback);
+ },
+};
+
+function callMmdbMethodLegacy(aMmdb, aMethodName) {
+ let deferred = Promise.defer();
+
+ let args = Array.slice(arguments, 2);
+ args.push({
+ notify: function(aRv) {
+ if (!Components.isSuccessCode(aRv)) {
+ ok(true, aMethodName + " returns a unsuccessful code: " + aRv);
+ deferred.reject(Array.slice(arguments));
+ } else {
+ ok(true, aMethodName + " returns a successful code: " + aRv);
+ deferred.resolve(Array.slice(arguments));
+ }
+ }
+ });
+ LEGACY[aMethodName].apply(aMmdb, args);
+
+ return deferred.promise;
+}
+
+function saveSendingMessageLegacy(aMmdb, aMessage) {
+ return callMmdbMethodLegacy(aMmdb, "saveSendingMessage", aMessage);
+}
+
+function saveReceivedMessageLegacy(aMmdb, aMessage) {
+ return callMmdbMethodLegacy(aMmdb, "saveReceivedMessage", aMessage);
+}
+
+// Have a long long subject causes the send fails, so we don't need
+// networking here.
+const MMS_MAX_LENGTH_SUBJECT = 40;
+function genMmsSubject(sep) {
+ return "Hello " + (new Array(MMS_MAX_LENGTH_SUBJECT).join(sep)) + " World!";
+}
+
+function generateMms(aSender, aReceivers, aDelivery) {
+ let message = {
+ headers: {},
+ type: "mms",
+ timestamp: Date.now(),
+ receivers: aReceivers,
+ subject: genMmsSubject(' '),
+ attachments: [],
+ };
+
+ message.headers.subject = message.subject;
+ message.headers.to = [];
+ for (let i = 0; i < aReceivers.length; i++) {
+ let receiver = aReceivers[i];
+ let entry = { type: MMS.Address.resolveType(receiver) };
+ if (entry.type == "PLMN") {
+ entry.address = PhoneNumberUtils.normalize(receiver, false);
+ } else {
+ entry.address = receiver;
+ }
+ ok(true, "MMS to address '" + receiver +"' resolved as type " + entry.type);
+ message.headers.to.push(entry);
+ }
+ if (aSender) {
+ message.headers.from = {
+ address: aSender,
+ type: MMS.Address.resolveType(aSender)
+ };
+ ok(true, "MMS from address '" + aSender +"' resolved as type " +
+ message.headers.from.type);
+ }
+
+ if ((aDelivery === DELIVERY_RECEIVED) ||
+ (aDelivery === DELIVERY_NOT_DOWNLOADED)) {
+ message.delivery = aDelivery;
+ message.deliveryStatus = DELIVERY_STATUS_SUCCESS;
+ } else {
+ message.deliveryStatusRequested = false;
+ }
+
+ return message;
+}
+
+function generateSms(aSender, aReceiver, aDelivery) {
+ let message = {
+ type: "sms",
+ sender: aSender,
+ timestamp: Date.now(),
+ receiver: aReceiver,
+ body: "The snow grows white on the mountain tonight.",
+ };
+
+ if (aDelivery === DELIVERY_RECEIVED) {
+ message.messageClass = MESSAGE_CLASS_NORMAL;
+ } else {
+ message.deliveryStatusRequested = false;
+ }
+
+ return message;
+}
+
+function matchArray(lhs, rhs) {
+ if (rhs.length != lhs.length) {
+ return false;
+ }
+
+ for (let k = 0; k < lhs.length; k++) {
+ if (lhs[k] != rhs[k]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+const TEST_ADDRESSES = [
+ "+15525225554", // MMS, TYPE=PLMN
+ "5525225554", // MMS, TYPE=PLMN
+ "jkalbcjklg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer
+ "jk.alb.cjk.lg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer
+ "j:k:a:l:b:c:jk:lg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer
+ "55.252.255.54", // MMS, TYPE=IPv4
+ "5:5:2:5:2:2:55:54", // MMS, TYPE=IPv6
+ "jk@alb.cjk.lg", // MMS, TYPE=email
+ "___" // MMS, TYPE=Others
+];
+
+function populateDatabase(aMmdb) {
+ log("Populating database:");
+
+ let promise = Promise.resolve()
+
+ // We're generating other messages that would be identified as the same
+ // participant with "+15525225554".
+ .then(() => saveReceivedMessageLegacy(
+ aMmdb, generateSms("+15525225554", null, DELIVERY_RECEIVED)))
+
+ // SMS, national number.
+ .then(() => saveReceivedMessageLegacy(
+ aMmdb, generateSms("5525225554", null, DELIVERY_RECEIVED)));
+
+ for (let i = 0; i < TEST_ADDRESSES.length; i++) {
+ let address = TEST_ADDRESSES[i];
+ promise = promise.then(() => saveReceivedMessageLegacy(
+ aMmdb, generateMms(address, ["a"], DELIVERY_RECEIVED)));
+ }
+
+ // Permutation of TEST_ADDRESSES.
+ for (let i = 0; i < TEST_ADDRESSES.length; i++) {
+ for (let j = i + 1; j < TEST_ADDRESSES.length; j++) {
+ let addr_i = TEST_ADDRESSES[i], addr_j = TEST_ADDRESSES[j];
+ promise = promise.then(() => saveSendingMessageLegacy(
+ aMmdb, generateMms(null, [addr_i, addr_j], DELIVERY_SENDING)));
+ }
+ }
+
+ // At this time, we have 3 threads, whose |participants| are:
+ // ["+15525225554"], ["___"], and ["+15525225554", "___"]. The number of each
+ // thread are [ (2 + 8 + 8 * 7 / 2), 1, 8 ] = [ 38, 1, 8 ].
+
+ return promise;
+}
+
+function doVerifyDatabase(aMmdb, aExpected) {
+ // 1) retrieve all threads.
+ return createThreadCursor(aMmdb)
+ .then(function(aValues) {
+ let [errorCode, domThreads] = aValues;
+ is(errorCode, 0, "errorCode");
+ is(domThreads.length, aExpected.length, "domThreads.length");
+
+ let numMessagesInThread = [];
+ let totalMessages = 0;
+
+ for (let i = 0; i < domThreads.length; i++) {
+ let domThread = domThreads[i];
+ log(" thread<" + domThread.id + "> : " + domThread.participants);
+
+ let index = (function() {
+ let rhs = domThread.participants;
+ for (let j = 0; j < aExpected.length; j++) {
+ let lhs = aExpected[j].participants;
+ if (matchArray(lhs, rhs)) {
+ return j;
+ }
+ }
+ })();
+ // 2) make sure all retrieved threads are in expected array.
+ ok(index >= 0, "validity of domThread.participants");
+
+ // 3) fill out numMessagesInThread, which is a =>
+ // map.
+ numMessagesInThread[domThread.id] = aExpected[index].messages;
+ totalMessages += aExpected[index].messages
+
+ aExpected.splice(index, 1);
+ }
+
+ // 4) make sure no thread is missing by checking |aExpected.length == 0|.
+ is(aExpected.length, 0, "remaining unmatched threads");
+
+ // 5) retrieve all messages.
+ return createMessageCursor(aMmdb, {})
+ .then(function(aValues) {
+ let [errorCode, domMessages] = aValues;
+ is(errorCode, 0, "errorCode");
+ // 6) check total number of messages.
+ is(domMessages.length, totalMessages, "domMessages.length");
+
+ for (let i = 0; i < domMessages.length; i++) {
+ let domMessage = domMessages[i];
+ // 7) make sure message thread id is valid by checking
+ // |numMessagesInThread[domMessage.threadId] != null|.
+ ok(numMessagesInThread[domMessage.threadId] != null,
+ "domMessage.threadId");
+
+ // 8) for each message, reduce
+ // |numMessagesInThread[domMessage.threadId]| by 1.
+ --numMessagesInThread[domMessage.threadId];
+ ok(true, "numMessagesInThread[" + domMessage.threadId + "] = " +
+ numMessagesInThread[domMessage.threadId]);
+ }
+
+ // 9) check if |numMessagesInThread| is now an array of all zeros.
+ for (let i = 0; i < domThreads.length; i++) {
+ let domThread = domThreads[i];
+ is(numMessagesInThread[domThread.id], 0,
+ "numMessagesInThread[" + domThread.id + "]");
+ }
+ });
+ });
+}
+
+function verifyDatabaseBeforeUpgrade(aMmdb) {
+ log("Before updateSchema22:");
+ return doVerifyDatabase(aMmdb, [{
+ participants: ["+15525225554"],
+ messages: 38 // 2 + (9 - 1) + (9 - 1) * ( 9 - 1 - 1) / 2
+ }, {
+ participants: ["___"],
+ messages: 1
+ }, {
+ participants: ["+15525225554", "___"],
+ messages: 8
+ }]);
+}
+
+function verifyDatabaseAfterUpgrade(aMmdb) {
+ log("After updateSchema22:");
+ return doVerifyDatabase(aMmdb, [{
+ participants: ["+15525225554"],
+ messages: 17 // 2 + 5 + 5 * (5 - 1) / 2
+ }, {
+ participants: ["55.252.255.54"],
+ messages: 1
+ }, {
+ participants: ["5:5:2:5:2:2:55:54"],
+ messages: 1
+ }, {
+ participants: ["jk@alb.cjk.lg"],
+ messages: 1
+ }, {
+ participants: ["___"],
+ messages: 1
+ }, {
+ participants: ["+15525225554", "55.252.255.54"],
+ messages: 5
+ }, {
+ participants: ["+15525225554", "5:5:2:5:2:2:55:54"],
+ messages: 5
+ }, {
+ participants: ["+15525225554", "jk@alb.cjk.lg"],
+ messages: 5
+ }, {
+ participants: ["+15525225554", "___"],
+ messages: 5
+ }, {
+ participants: ["55.252.255.54", "5:5:2:5:2:2:55:54"],
+ messages: 1
+ }, {
+ participants: ["55.252.255.54", "jk@alb.cjk.lg"],
+ messages: 1
+ }, {
+ participants: ["55.252.255.54", "___"],
+ messages: 1
+ }, {
+ participants: ["5:5:2:5:2:2:55:54", "jk@alb.cjk.lg"],
+ messages: 1
+ }, {
+ participants: ["5:5:2:5:2:2:55:54", "___"],
+ messages: 1
+ }, {
+ participants: ["jk@alb.cjk.lg", "___"],
+ messages: 1
+ }]);
+}
+
+startTestBase(function testCaseMain() {
+ let mmdb = newMobileMessageDB();
+ return initMobileMessageDB(mmdb, DBNAME, 22)
+ .then(() => populateDatabase(mmdb))
+ .then(() => verifyDatabaseBeforeUpgrade(mmdb))
+ .then(() => closeMobileMessageDB(mmdb))
+
+ .then(() => initMobileMessageDB(mmdb, DBNAME, 23))
+ .then(() => verifyDatabaseAfterUpgrade(mmdb))
+ .then(() => closeMobileMessageDB(mmdb));
+});
diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js
new file mode 100644
index 00000000000..ff3a3288055
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js
@@ -0,0 +1,171 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'mmdb_head.js';
+
+const DBNAME = "test_mmdb_upgradeSchema_current_structure:" + newUUID();
+
+const LAYOUT = {
+ sms: {
+ keyPath: "id",
+ autoIncrement: false,
+ indice: {
+ delivery: {
+ keyPath: "deliveryIndex",
+ multiEntry: false,
+ unique: false,
+ },
+
+ envelopeId: {
+ keyPath: "envelopeIdIndex",
+ multiEntry: false,
+ unique: true,
+ },
+
+ participantIds: {
+ keyPath: "participantIdsIndex",
+ multiEntry: true,
+ unique: false,
+ },
+
+ read: {
+ keyPath: "readIndex",
+ multiEntry: false,
+ unique: false,
+ },
+
+ threadId: {
+ keyPath: "threadIdIndex",
+ multiEntry: false,
+ unique: false,
+ },
+
+ timestamp: {
+ keyPath: "timestamp",
+ multiEntry: false,
+ unique: false,
+ },
+
+ transactionId: {
+ keyPath: "transactionIdIndex",
+ multiEntry: false,
+ unique: true,
+ }
+ },
+ },
+
+ thread: {
+ keyPath: "id",
+ autoIncrement: true,
+ indice: {
+ lastTimestamp: {
+ keyPath: "lastTimestamp",
+ multiEntry: false,
+ unique: false,
+ },
+
+ participantIds: {
+ keyPath: "participantIds",
+ multiEntry: false,
+ unique: false,
+ }
+ },
+ },
+
+ participant: {
+ keyPath: "id",
+ autoIncrement: true,
+ indice: {
+ addresses: {
+ keyPath: "addresses",
+ multiEntry: true,
+ unique: false,
+ }
+ },
+ },
+
+ "sms-segment": {
+ keyPath: "id",
+ autoIncrement: true,
+ indice: {
+ hash: {
+ keyPath: "hash",
+ multiEntry: false,
+ unique: true,
+ }
+ },
+ }
+};
+
+function verifyIndex(aIndex, aIndexLayout) {
+ log(" Verifying index '" + aIndex.name + "'");
+
+ is(aIndex.keyPath, aIndexLayout.keyPath, "aIndex.keyPath");
+ is(aIndex.multiEntry, aIndexLayout.multiEntry, "aIndex.multiEntry");
+ is(aIndex.unique, aIndexLayout.unique, "aIndex.unique");
+}
+
+function verifyStore(aObjectStore, aStoreLayout) {
+ log("Verifying object store '" + aObjectStore.name + "'");
+
+ is(aObjectStore.keyPath, aStoreLayout.keyPath, "aObjectStore.keyPath");
+ is(aObjectStore.autoIncrement, aStoreLayout.autoIncrement,
+ "aObjectStore.autoIncrement");
+
+ let expectedIndexNames = Object.keys(aStoreLayout.indice);
+ for (let i = 0; i < aObjectStore.indexNames.length; i++) {
+ let indexName = aObjectStore.indexNames.item(i);
+
+ let index = expectedIndexNames.indexOf(indexName);
+ ok(index >= 0, "Index name '" + indexName + "' validity");
+ expectedIndexNames.splice(index, 1);
+
+ verifyIndex(aObjectStore.index(indexName), aStoreLayout.indice[indexName]);
+ }
+
+ // All index names should have been verified and leaves expectedIndexNames an
+ // empty array.
+ is(expectedIndexNames.length, 0, "Extra indice: " + expectedIndexNames);
+}
+
+function verifyDatabase(aMmdb) {
+ let deferred = Promise.defer();
+
+ let expectedStoreNames = Object.keys(LAYOUT);
+ aMmdb.newTxn("readonly", function(aError, aTransaction, aObjectStores) {
+ if (!Array.isArray(aObjectStores)) {
+ // When we have only one object store open, aObjectStores is an instance
+ // of IDBObjectStore. Push it to an array for convenience here.
+ aObjectStores = [aObjectStores];
+ }
+
+ is(aObjectStores.length, expectedStoreNames.length,
+ "expected number of object stores");
+
+ let slicedStoreNames = expectedStoreNames.slice();
+ for (let i = 0; i < aObjectStores.length; i++) {
+ let objectStore = aObjectStores[i];
+
+ let index = slicedStoreNames.indexOf(objectStore.name);
+ ok(index >= 0, "objectStore.name '" + objectStore.name + "' validity");
+ slicedStoreNames.splice(index, 1);
+
+ verifyStore(objectStore, LAYOUT[objectStore.name]);
+ }
+
+ // All store names should have been verified and leaves slicedStoreNames an
+ // empty array.
+ is(slicedStoreNames.length, 0, "Extra object stores: " + slicedStoreNames);
+
+ deferred.resolve(aMmdb);
+ }, expectedStoreNames);
+
+ return deferred.promise;
+}
+
+startTestBase(function testCaseMain() {
+ return initMobileMessageDB(newMobileMessageDB(), DBNAME, 0)
+ .then(verifyDatabase)
+ .then(closeMobileMessageDB);
+});
diff --git a/dom/mobilemessage/tests/test_mms_pdu_helper.js b/dom/mobilemessage/tests/test_mms_pdu_helper.js
index 1b1806da905..6f01e27eb23 100644
--- a/dom/mobilemessage/tests/test_mms_pdu_helper.js
+++ b/dom/mobilemessage/tests/test_mms_pdu_helper.js
@@ -50,30 +50,155 @@ add_test(function test_Address_decode() {
{address: "+123.456-789", type: "PLMN"});
wsp_decode_test(MMS.Address, strToCharCodeArray("123456789/TYPE=PLMN"),
{address: "123456789", type: "PLMN"});
+
+ wsp_decode_test(MMS.Address, strToCharCodeArray("a23456789/TYPE=PLMN"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("++123456789/TYPE=PLMN"),
+ null, "CodeError");
+
// Test for IPv4
wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.4/TYPE=IPv4"),
{address: "1.2.3.4", type: "IPv4"});
+
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.256/TYPE=IPv4"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.00/TYPE=IPv4"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.a/TYPE=IPv4"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3/TYPE=IPv4"),
+ null, "CodeError");
+
// Test for IPv6
wsp_decode_test(MMS.Address,
strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6"),
{address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"}
);
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6:7:8/TYPE=IPv6"),
+ {address: "1:2:3:4:5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6:7::/TYPE=IPv6"),
+ {address: "1:2:3:4:5:6:7::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6::/TYPE=IPv6"),
+ {address: "1:2:3:4:5:6::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5::/TYPE=IPv6"),
+ {address: "1:2:3:4:5::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::/TYPE=IPv6"),
+ {address: "1:2:3:4::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::/TYPE=IPv6"),
+ {address: "1:2:3::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::/TYPE=IPv6"),
+ {address: "1:2::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::/TYPE=IPv6"),
+ {address: "1::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::/TYPE=IPv6"),
+ {address: "::", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6::8/TYPE=IPv6"),
+ {address: "1:2:3:4:5:6::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5::8/TYPE=IPv6"),
+ {address: "1:2:3:4:5::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::8/TYPE=IPv6"),
+ {address: "1:2:3:4::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::8/TYPE=IPv6"),
+ {address: "1:2:3::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::8/TYPE=IPv6"),
+ {address: "1:2::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::8/TYPE=IPv6"),
+ {address: "1::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::8/TYPE=IPv6"),
+ {address: "::8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5::7:8/TYPE=IPv6"),
+ {address: "1:2:3:4:5::7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::7:8/TYPE=IPv6"),
+ {address: "1:2:3:4::7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::7:8/TYPE=IPv6"),
+ {address: "1:2:3::7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::7:8/TYPE=IPv6"),
+ {address: "1:2::7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::7:8/TYPE=IPv6"),
+ {address: "1::7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::7:8/TYPE=IPv6"),
+ {address: "::7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::6:7:8/TYPE=IPv6"),
+ {address: "1:2:3:4::6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::6:7:8/TYPE=IPv6"),
+ {address: "1:2:3::6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::6:7:8/TYPE=IPv6"),
+ {address: "1:2::6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::6:7:8/TYPE=IPv6"),
+ {address: "1::6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::6:7:8/TYPE=IPv6"),
+ {address: "::6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::5:6:7:8/TYPE=IPv6"),
+ {address: "1:2:3::5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::5:6:7:8/TYPE=IPv6"),
+ {address: "1:2::5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::5:6:7:8/TYPE=IPv6"),
+ {address: "1::5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::5:6:7:8/TYPE=IPv6"),
+ {address: "::5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::4:5:6:7:8/TYPE=IPv6"),
+ {address: "1:2::4:5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::4:5:6:7:8/TYPE=IPv6"),
+ {address: "1::4:5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::4:5:6:7:8/TYPE=IPv6"),
+ {address: "::4:5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::3:4:5:6:7:8/TYPE=IPv6"),
+ {address: "1::3:4:5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::3:4:5:6:7:8/TYPE=IPv6"),
+ {address: "::3:4:5:6:7:8", type: "IPv6"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("::2:3:4:5:6:7:8/TYPE=IPv6"),
+ {address: "::2:3:4:5:6:7:8", type: "IPv6"});
+
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1:g:3:4:5:6:7:8/TYPE=IPv6"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1::3:4::6:7:8/TYPE=IPv6"),
+ null, "CodeError");
+
// Test for other device-address
- wsp_decode_test(MMS.Address, strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_"),
- {address: "+H-e.l%l_o", type: "W0r1d_"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("+H-e.1%l_o/TYPE=W0r1d_"),
+ {address: "+H-e.1%l_o", type: "W0r1d_"});
+
+ wsp_decode_test(MMS.Address, strToCharCodeArray("addr/TYPE=type!"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("addr!/TYPE=type"),
+ null, "CodeError");
+
// Test for num-shortcode
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1"),
+ {address: "1", type: "num"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("123"),
+ {address: "123", type: "num"});
wsp_decode_test(MMS.Address, strToCharCodeArray("+123"),
{address: "+123", type: "num"});
wsp_decode_test(MMS.Address, strToCharCodeArray("*123"),
{address: "*123", type: "num"});
wsp_decode_test(MMS.Address, strToCharCodeArray("#123"),
{address: "#123", type: "num"});
+
+ wsp_decode_test(MMS.Address, strToCharCodeArray("++123"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("!123"),
+ null, "CodeError");
+ wsp_decode_test(MMS.Address, strToCharCodeArray("1*23"),
+ null, "CodeError");
+
// Test for alphanum-shortcode
- wsp_decode_test(MMS.Address, strToCharCodeArray("H0wD0Y0uTurnTh1s0n"),
- {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("a"),
+ {address: "a", type: "alphanum"});
+ wsp_decode_test(MMS.Address, strToCharCodeArray("H0w_D0_Y0u_Turn_Th1s_0n"),
+ {address: "H0w_D0_Y0u_Turn_Th1s_0n", type: "alphanum"});
+
+ wsp_decode_test(MMS.Address, strToCharCodeArray("abc#"),
+ null, "CodeError");
+
// Test for email address
wsp_decode_test(MMS.Address, strToCharCodeArray("Joe User "),
{address: "Joe User ", type: "email"});
+ wsp_decode_test(MMS.Address,
+ strToCharCodeArray("a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-"),
+ {address: "a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-", type: "email"}
+ );
+
// Test for invalid address
wsp_decode_test(MMS.Address, strToCharCodeArray("@@@@@"),
null, "CodeError");
@@ -89,30 +214,182 @@ add_test(function test_Address_encode() {
strToCharCodeArray("+123.456-789/TYPE=PLMN"));
wsp_encode_test(MMS.Address, {address: "123456789", type: "PLMN"},
strToCharCodeArray("123456789/TYPE=PLMN"));
+
+ wsp_encode_test(MMS.Address, {address: "a23456789", type: "PLMN"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "++123456789", type: "PLMN"},
+ null, "CodeError");
+
// Test for IPv4
wsp_encode_test(MMS.Address, {address: "1.2.3.4", type: "IPv4"},
strToCharCodeArray("1.2.3.4/TYPE=IPv4"));
+
+ wsp_encode_test(MMS.Address, {address: "1.2.3.256", type: "IPv4"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "1.2.3.00", type: "IPv4"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "1.2.3.a", type: "IPv4"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "1.2.3", type: "IPv4"},
+ null, "CodeError");
+
// Test for IPv6
wsp_encode_test(MMS.Address,
{address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"},
strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6")
);
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6:7::", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5:6:7::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6::", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5:6::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5::", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4::", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3::", type: "IPv6"},
+ strToCharCodeArray("1:2:3::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2::", type: "IPv6"},
+ strToCharCodeArray("1:2::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::", type: "IPv6"},
+ strToCharCodeArray("1::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::", type: "IPv6"},
+ strToCharCodeArray("::/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6::8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5:6::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5::8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4::8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3::8", type: "IPv6"},
+ strToCharCodeArray("1:2:3::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2::8", type: "IPv6"},
+ strToCharCodeArray("1:2::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::8", type: "IPv6"},
+ strToCharCodeArray("1::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::8", type: "IPv6"},
+ strToCharCodeArray("::8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4:5::7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4:5::7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4::7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4::7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3::7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3::7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2::7:8", type: "IPv6"},
+ strToCharCodeArray("1:2::7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::7:8", type: "IPv6"},
+ strToCharCodeArray("1::7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::7:8", type: "IPv6"},
+ strToCharCodeArray("::7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3:4::6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3:4::6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3::6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3::6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2::6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2::6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::6:7:8", type: "IPv6"},
+ strToCharCodeArray("1::6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::6:7:8", type: "IPv6"},
+ strToCharCodeArray("::6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2:3::5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2:3::5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2::5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2::5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1::5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("::5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1:2::4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1:2::4:5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1::4:5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("::4:5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "1::3:4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("1::3:4:5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::3:4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("::3:4:5:6:7:8/TYPE=IPv6"));
+ wsp_encode_test(MMS.Address, {address: "::2:3:4:5:6:7:8", type: "IPv6"},
+ strToCharCodeArray("::2:3:4:5:6:7:8/TYPE=IPv6"));
+
+ wsp_encode_test(MMS.Address, {address: "1:g:3:4:5:6:7:8", type: "IPv6"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "1::3:4:5:6::8", type: "IPv6"},
+ null, "CodeError");
+
// Test for other device-address
- wsp_encode_test(MMS.Address, {address: "+H-e.l%l_o", type: "W0r1d_"},
- strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_"));
+ wsp_encode_test(MMS.Address, {address: "+H-e.1%l_o", type: "W0r1d_"},
+ strToCharCodeArray("+H-e.1%l_o/TYPE=W0r1d_"));
+
+ wsp_encode_test(MMS.Address, {address: "addr!", type: "type"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "addr", type: "type!"},
+ null, "CodeError");
+
// Test for num-shortcode
+ wsp_encode_test(MMS.Address, {address: "1", type: "num"},
+ strToCharCodeArray("1"));
+ wsp_encode_test(MMS.Address, {address: "123", type: "num"},
+ strToCharCodeArray("123"));
wsp_encode_test(MMS.Address, {address: "+123", type: "num"},
strToCharCodeArray("+123"));
wsp_encode_test(MMS.Address, {address: "*123", type: "num"},
strToCharCodeArray("*123"));
wsp_encode_test(MMS.Address, {address: "#123", type: "num"},
strToCharCodeArray("#123"));
+
+ wsp_encode_test(MMS.Address, {address: "++123", type: "num"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "!123", type: "num"},
+ null, "CodeError");
+ wsp_encode_test(MMS.Address, {address: "1#23", type: "num"},
+ null, "CodeError");
+
// Test for alphanum-shortcode
- wsp_encode_test(MMS.Address, {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"},
- strToCharCodeArray("H0wD0Y0uTurnTh1s0n"));
+ wsp_encode_test(MMS.Address, {address: "a", type: "alphanum"},
+ strToCharCodeArray("a"));
+ wsp_encode_test(MMS.Address, {address: "1", type: "alphanum"},
+ strToCharCodeArray("1"));
+ wsp_encode_test(MMS.Address, {address: "H0w_D0_Y0u_Turn_Th1s_0n", type: "alphanum"},
+ strToCharCodeArray("H0w_D0_Y0u_Turn_Th1s_0n"));
+
+ wsp_encode_test(MMS.Address, {address: "abc#", type: "alphanum"},
+ null, "CodeError");
+
// Test for email address
wsp_encode_test(MMS.Address, {address: "Joe User ", type: "email"},
strToCharCodeArray("Joe User "));
+ wsp_encode_test(MMS.Address,
+ {address: "a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-", type: "email"},
+ strToCharCodeArray("a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-")
+ );
+
+ // Test for invalid address
+ wsp_encode_test(MMS.Address, {address: "a"}, null, "CodeError");
+ wsp_encode_test(MMS.Address, {type: "alphanum"}, null, "CodeError");
+
+ run_next_test();
+});
+
+//// Address.resolveType ////
+
+add_test(function test_Address_encode() {
+ function test(address, type) {
+ do_check_eq(MMS.Address.resolveType(address), type);
+ }
+
+ // Test ambiguous addresses.
+ test("+15525225554", "PLMN");
+ test("5525225554", "PLMN");
+ test("jkalbcjklg", "PLMN");
+ test("jk.alb.cjk.lg", "PLMN");
+ test("j:k:a:l:b:c:jk:lg", "PLMN");
+ test("55.252.255.54", "IPv4");
+ test("5:5:2:5:2:2:55:54", "IPv6");
+ test("jk@alb.cjk.lg", "email");
+ // Test empty address. This is for received anonymous MMS messages.
+ test("", "Others");
run_next_test();
});
diff --git a/mobile/android/base/toolbar/ToolbarEditText.java b/mobile/android/base/toolbar/ToolbarEditText.java
index 30a8efd82eb..19b8627c32d 100644
--- a/mobile/android/base/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/toolbar/ToolbarEditText.java
@@ -11,14 +11,12 @@ import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
import org.mozilla.gecko.CustomEditText;
import org.mozilla.gecko.CustomEditText.OnKeyPreImeListener;
-import org.mozilla.gecko.InputMethods;
import org.mozilla.gecko.util.GamepadUtils;
import org.mozilla.gecko.util.StringUtils;
import android.content.Context;
import android.graphics.Rect;
import android.text.Editable;
-import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -27,13 +25,11 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
-import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
/**
* {@code ToolbarEditText} is the text entry used when the toolbar
-* is in edit state. It handles all the necessary input method machinery
-* as well as the tracking of different text types (empty, search, or url).
+* is in edit state. It handles all the necessary input method machinery.
* It's meant to be owned by {@code ToolbarEditLayout}.
*/
public class ToolbarEditText extends CustomEditText
@@ -41,28 +37,11 @@ public class ToolbarEditText extends CustomEditText
private static final String LOGTAG = "GeckoToolbarEditText";
- // Used to track the current type of content in the
- // text entry so that ToolbarEditLayout can update its
- // state accordingly.
- enum TextType {
- EMPTY,
- SEARCH_QUERY,
- URL
- }
-
- interface OnTextTypeChangeListener {
- public void onTextTypeChange(ToolbarEditText editText, TextType textType);
- }
-
private final Context mContext;
- // Type of the URL bar go/search button
- private TextType mToolbarTextType;
-
private OnCommitListener mCommitListener;
private OnDismissListener mDismissListener;
private OnFilterListener mFilterListener;
- private OnTextTypeChangeListener mTextTypeListener;
// The previous autocomplete result returned to us
private String mAutoCompleteResult = "";
@@ -73,8 +52,6 @@ public class ToolbarEditText extends CustomEditText
public ToolbarEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
-
- mToolbarTextType = TextType.EMPTY;
}
void setOnCommitListener(OnCommitListener listener) {
@@ -89,10 +66,6 @@ public class ToolbarEditText extends CustomEditText
mFilterListener = listener;
}
- void setOnTextTypeChangeListener(OnTextTypeChangeListener listener) {
- mTextTypeListener = listener;
- }
-
@Override
public void onAttachedToWindow() {
setOnKeyListener(new KeyListener());
@@ -144,12 +117,6 @@ public class ToolbarEditText extends CustomEditText
setSelection(text.length(), result.length());
}
- @Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
- updateTextTypeFromText(getText().toString());
- }
-
private void resetAutocompleteState() {
mAutoCompleteResult = "";
mAutoCompletePrefix = null;
@@ -170,29 +137,6 @@ public class ToolbarEditText extends CustomEditText
return false;
}
- private void setTextType(TextType textType) {
- mToolbarTextType = textType;
-
- if (mTextTypeListener != null) {
- mTextTypeListener.onTextTypeChange(this, textType);
- }
- }
-
- private void updateTextTypeFromText(String text) {
- if (text.length() == 0) {
- setTextType(TextType.EMPTY);
- return;
- }
-
- final TextType newType;
- if (StringUtils.isSearchQuery(text, mToolbarTextType == TextType.SEARCH_QUERY)) {
- newType = TextType.SEARCH_QUERY;
- } else {
- newType = TextType.URL;
- }
- setTextType(newType);
- }
-
private class TextChangeListener implements TextWatcher {
@Override
public void afterTextChanged(final Editable s) {
@@ -232,8 +176,6 @@ public class ToolbarEditText extends CustomEditText
onAutocomplete(mAutoCompleteResult);
}
}
-
- updateTextTypeFromText(text);
}
@Override