diff --git a/b2g/config/mozconfigs/linux32/debug b/b2g/config/mozconfigs/linux32/debug index ef038e4827c0..b89fe59b7af6 100644 --- a/b2g/config/mozconfigs/linux32/debug +++ b/b2g/config/mozconfigs/linux32/debug @@ -2,9 +2,9 @@ #export GONK_PRODUCT=generic #gonk="/home/cjones/mozilla/gonk-toolchain-$GONK_TOOLCHAIN_VERSION" -mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/objdir-prof-gonk +mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-b2g -mk_add_options MOZ_MAKE_FLAGS="-s -j16" +mk_add_options MOZ_MAKE_FLAGS="-j8" ac_add_options --enable-application=b2g diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml index 27adc22e6597..42295e932ba1 100644 --- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -1,5 +1,5 @@ - + @@ -65,8 +65,8 @@ - - + + @@ -153,6 +153,10 @@ + + + + diff --git a/browser/base/content/abouthome/aboutHome.css b/browser/base/content/abouthome/aboutHome.css index 21ccb745ea2b..b72ed0038ac4 100644 --- a/browser/base/content/abouthome/aboutHome.css +++ b/browser/base/content/abouthome/aboutHome.css @@ -229,6 +229,9 @@ body[narrow] #launcher[session] { padding: 14px 6px; min-width: 88px; max-width: 176px; + max-height: 85px; + vertical-align: top; + white-space: normal; background: transparent padding-box; border: 1px solid transparent; border-radius: 2.5px; @@ -241,9 +244,6 @@ body[narrow] #launcher[session] { body[narrow] #launcher[session] > .launchButton { margin: 4px 1px; - max-height: 85px; - vertical-align: top; - white-space: normal; } .launchButton:hover { diff --git a/browser/base/content/browser-thumbnails.js b/browser/base/content/browser-thumbnails.js index 325be9550595..f7fda70b356a 100644 --- a/browser/base/content/browser-thumbnails.js +++ b/browser/base/content/browser-thumbnails.js @@ -110,6 +110,15 @@ let gBrowserThumbnails = { let channel = aBrowser.docShell.currentDocumentChannel; + // No valid document channel. We shouldn't take a screenshot. + if (!channel) + return false; + + // Don't take screenshots of internally redirecting about: pages. + // This includes error pages. + if (channel.originalURI.schemeIs("about")) + return false; + try { // If the channel is a nsIHttpChannel get its http status code. let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); diff --git a/browser/base/content/newtab/drop.js b/browser/base/content/newtab/drop.js index 094b3acabcac..f3d45db465fa 100644 --- a/browser/base/content/newtab/drop.js +++ b/browser/base/content/newtab/drop.js @@ -94,7 +94,11 @@ let gDrop = { // A new link was dragged onto the grid. Create it by pinning its URL. let dt = aEvent.dataTransfer; let [url, title] = dt.getData("text/x-moz-url").split(/[\r\n]+/); - gPinnedLinks.pin({url: url, title: title}, index); + let link = {url: url, title: title}; + gPinnedLinks.pin(link, index); + + // Make sure the newly added link is not blocked. + gBlockedLinks.unblock(link); } }, diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index f6f44210dfde..34e361ecd7b1 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -92,6 +92,7 @@ nsContextMenu.prototype = { } catch (e) { } this.isTextSelected = this.isTextSelection(); this.isContentSelected = this.isContentSelection(); + this.onPlainTextLink = false; // Initialize (disable/remove) menu items. this.initItems(); @@ -132,7 +133,6 @@ nsContextMenu.prototype = { // Time to do some bad things and see if we've highlighted a URL that // isn't actually linked. - var onPlainTextLink = false; if (this.isTextSelected && !this.onLink) { // Ok, we have some text, let's figure out if it looks like a URL. let selection = document.commandDispatcher.focusedWindow @@ -190,14 +190,14 @@ nsContextMenu.prototype = { if (uri && uri.host) { this.linkURI = uri; this.linkURL = this.linkURI.spec; - onPlainTextLink = true; + this.onPlainTextLink = true; } } - var shouldShow = this.onSaveableLink || isMailtoInternal || onPlainTextLink; + var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink; this.showItem("context-openlink", shouldShow); this.showItem("context-openlinkintab", shouldShow); - this.showItem("context-openlinkincurrent", onPlainTextLink); + this.showItem("context-openlinkincurrent", this.onPlainTextLink); this.showItem("context-sep-open", shouldShow); }, @@ -222,9 +222,9 @@ nsContextMenu.prototype = { this.showItem("context-savepage", shouldShow); this.showItem("context-sendpage", shouldShow); - // Save+Send link depends on whether we're in a link. - this.showItem("context-savelink", this.onSaveableLink); - this.showItem("context-sendlink", this.onSaveableLink); + // Save+Send link depends on whether we're in a link, or selected text matches valid URL pattern. + this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink); + this.showItem("context-sendlink", this.onSaveableLink || this.onPlainTextLink); // Save image depends on having loaded its content, video and audio don't. this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas); @@ -310,7 +310,7 @@ nsContextMenu.prototype = { this.showItem("context-bookmarkpage", !(this.isContentSelected || this.onTextInput || this.onLink || this.onImage || this.onVideo || this.onAudio)); - this.showItem("context-bookmarklink", this.onLink && !this.onMailtoLink); + this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink) || this.onPlainTextLink); this.showItem("context-searchselect", isTextSelected); this.showItem("context-keywordfield", this.onTextInput && this.onKeywordField); @@ -1073,9 +1073,15 @@ nsContextMenu.prototype = { // Save URL of clicked-on link. saveLink: function() { var doc = this.target.ownerDocument; + var linkText; + // If selected text is found to match valid URL pattern. + if (this.onPlainTextLink) + linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim(); + else + linkText = this.linkText(); urlSecurityCheck(this.linkURL, doc.nodePrincipal); - this.saveHelper(this.linkURL, this.linkText(), null, true, doc); + this.saveHelper(this.linkURL, linkText, null, true, doc); }, sendLink: function() { @@ -1390,8 +1396,14 @@ nsContextMenu.prototype = { }, bookmarkLink: function CM_bookmarkLink() { + var linkText; + // If selected text is found to match valid URL pattern. + if (this.onPlainTextLink) + linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim(); + else + linkText = this.linkText(); window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL, - this.linkText()); + linkText); }, addBookmarkForFrame: function CM_addBookmarkForFrame() { diff --git a/browser/base/content/test/newtab/Makefile.in b/browser/base/content/test/newtab/Makefile.in index 8a81c95d119f..668c839730ec 100644 --- a/browser/base/content/test/newtab/Makefile.in +++ b/browser/base/content/test/newtab/Makefile.in @@ -25,6 +25,7 @@ _BROWSER_FILES = \ browser_newtab_bug723121.js \ browser_newtab_bug725996.js \ browser_newtab_bug734043.js \ + browser_newtab_bug735987.js \ head.js \ $(NULL) diff --git a/browser/base/content/test/newtab/browser_newtab_bug735987.js b/browser/base/content/test/newtab/browser_newtab_bug735987.js new file mode 100644 index 000000000000..4154d1da8fec --- /dev/null +++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function runTests() { + setLinks("0,1,2,3,4,5,6,7,8"); + setPinnedLinks(""); + + yield addNewTabPageTab(); + checkGrid("0,1,2,3,4,5,6,7,8"); + + yield simulateDrop(cells[1]); + checkGrid("0,99p,1,2,3,4,5,6,7"); + + yield blockCell(cells[1]); + checkGrid("0,1,2,3,4,5,6,7,8"); + + yield simulateDrop(cells[1]); + checkGrid("0,99p,1,2,3,4,5,6,7"); + + yield blockCell(cells[1]); + checkGrid("0,1,2,3,4,5,6,7,8"); +} diff --git a/browser/base/content/test/subtst_contextmenu.html b/browser/base/content/test/subtst_contextmenu.html index ad1b7c2375bb..68b3e69139b4 100644 --- a/browser/base/content/test/subtst_contextmenu.html +++ b/browser/base/content/test/subtst_contextmenu.html @@ -58,6 +58,8 @@ Browser context menu subtest. +
Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+ diff --git a/browser/base/content/test/test_contextmenu.html b/browser/base/content/test/test_contextmenu.html index dd832d551f32..15bd018bfc7c 100644 --- a/browser/base/content/test/test_contextmenu.html +++ b/browser/base/content/test/test_contextmenu.html @@ -20,6 +20,7 @@ Browser context menu tests. netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); const Cc = Components.classes; const Ci = Components.interfaces; @@ -72,6 +73,16 @@ function invokeItemAction(generatedItemId) ok(!pagemenu.hasAttribute("hopeless"), "attribute got removed"); } +function selectText(element) { + // Clear any previous selections before selecting new element. + subwindow.getSelection().removeAllRanges(); + + var div = subwindow.document.createRange(); + div.setStartBefore(element); + div.setEndAfter(element); + subwindow.getSelection().addRange(div); +} + function getVisibleMenuItems(aMenu, aData) { var items = []; var accessKeys = {}; @@ -667,6 +678,45 @@ function runTest(testNum) { "context-viewinfo", true ].concat(inspectItems)); closeContextMenu(); + selectText(selecttext); // Select text prior to opening context menu. + openContextMenuFor(selecttext); // Invoke context menu for next test. + return; + + case 22: + // Context menu for selected text + if (Services.appinfo.OS == "Darwin") { + // This test is only enabled on Mac due to bug 736399. + checkContextMenu(["context-copy", true, + "context-selectall", true, + "---", null, + "context-searchselect", true, + "context-viewpartialsource-selection", true + ].concat(inspectItems)); + } + closeContextMenu(); + selectText(selecttextlink); // Select text prior to opening context menu. + openContextMenuFor(selecttextlink); // Invoke context menu for next test. + return; + + case 23: + // Context menu for selected text which matches valid URL pattern + if (Services.appinfo.OS == "Darwin") { + // This test is only enabled on Mac due to bug 736399. + checkContextMenu(["context-openlinkincurrent", true, + "context-openlinkintab", true, + "context-openlink", true, + "---", null, + "context-bookmarklink", true, + "context-savelink", true, + "context-sendlink", true, + "context-copy", true, + "context-selectall", true, + "---", null, + "context-searchselect", true, + "context-viewpartialsource-selection", true + ].concat(inspectItems)); + } + closeContextMenu(); subwindow.close(); SimpleTest.finish(); @@ -674,7 +724,6 @@ function runTest(testNum) { /* * Other things that would be nice to test: - * - selected text * - spelling / misspelled word (in text input?) * - check state of disabled items * - test execution of menu items (maybe as a separate test?) @@ -734,6 +783,8 @@ function startTest() { contenteditable.focus(); // content editable needs to be focused to enable spellcheck inputspell = subwindow.document.getElementById("test-input-spellcheck"); pagemenu = subwindow.document.getElementById("test-pagemenu"); + selecttext = subwindow.document.getElementById("test-select-text"); + selecttextlink = subwindow.document.getElementById("test-select-text-link"); contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false); runTest(1); diff --git a/browser/components/thumbnails/PageThumbs.jsm b/browser/components/thumbnails/PageThumbs.jsm index ca98a03d878f..3512b89d6236 100644 --- a/browser/components/thumbnails/PageThumbs.jsm +++ b/browser/components/thumbnails/PageThumbs.jsm @@ -109,6 +109,7 @@ let PageThumbs = { * @param aCallback The function to be called when finished (optional). */ captureAndStore: function PageThumbs_captureAndStore(aBrowser, aCallback) { + let url = aBrowser.currentURI.spec; this.capture(aBrowser.contentWindow, function (aInputStream) { let telemetryStoreTime = new Date(); @@ -123,7 +124,7 @@ let PageThumbs = { } // Get a writeable cache entry. - PageThumbsCache.getWriteEntry(aBrowser.currentURI.spec, function (aEntry) { + PageThumbsCache.getWriteEntry(url, function (aEntry) { if (!aEntry) { finish(false); return; diff --git a/browser/components/thumbnails/test/Makefile.in b/browser/components/thumbnails/test/Makefile.in index 03fea12b2770..0ebea85cebbd 100644 --- a/browser/components/thumbnails/test/Makefile.in +++ b/browser/components/thumbnails/test/Makefile.in @@ -13,6 +13,7 @@ include $(topsrcdir)/config/rules.mk _BROWSER_FILES = \ browser_thumbnails_capture.js \ + browser_thumbnails_bug726727.js \ head.js \ $(NULL) diff --git a/browser/components/thumbnails/test/browser_thumbnails_bug726727.js b/browser/components/thumbnails/test/browser_thumbnails_bug726727.js new file mode 100644 index 000000000000..68604a993462 --- /dev/null +++ b/browser/components/thumbnails/test/browser_thumbnails_bug726727.js @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * These tests ensure that capturing a sites's thumbnail, saving it and + * retrieving it from the cache works. + */ +function runTests() { + // Create a tab that shows an error page. + let tab = gBrowser.addTab("http://non-existant.url/"); + let browser = tab.linkedBrowser; + + yield browser.addEventListener("DOMContentLoaded", function onLoad() { + browser.removeEventListener("DOMContentLoaded", onLoad, false); + executeSoon(next); + }, false); + + ok(!gBrowserThumbnails._shouldCapture(browser), "we're not going to capture an error page"); +} diff --git a/browser/modules/NewTabUtils.jsm b/browser/modules/NewTabUtils.jsm index e2fff6cd50ea..12dd983f2b59 100644 --- a/browser/modules/NewTabUtils.jsm +++ b/browser/modules/NewTabUtils.jsm @@ -383,6 +383,15 @@ let BlockedLinks = { Storage.set("blockedLinks", this.links); }, + /** + * Unblocks a given link. + * @param aLink The link to unblock. + */ + unblock: function BlockedLinks_unblock(aLink) { + if (this.isBlocked(aLink)) + delete this.links[aLink.url]; + }, + /** * Returns whether a given link is blocked. * @param aLink The link to check. diff --git a/dom/sms/src/ril/SmsDatabaseService.js b/dom/sms/src/ril/SmsDatabaseService.js index e641352565b0..880466f99429 100644 --- a/dom/sms/src/ril/SmsDatabaseService.js +++ b/dom/sms/src/ril/SmsDatabaseService.js @@ -23,6 +23,11 @@ const FILTER_TIMESTAMP = "timestamp"; const FILTER_NUMBERS = "numbers"; const FILTER_DELIVERY = "delivery"; +const READ_ONLY = "readonly"; +const READ_WRITE = "readwrite"; +const PREV = "prev"; +const NEXT = "next"; + XPCOMUtils.defineLazyServiceGetter(this, "gSmsService", "@mozilla.org/sms/smsservice;1", "nsISmsService"); @@ -44,13 +49,13 @@ function SmsDatabaseService() { gIDBManager.initWindowless(GLOBAL_SCOPE); let that = this; - this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function(error, txn, store){ + this.newTxn(READ_ONLY, function(error, txn, store){ if (error) { return; } // In order to get the highest key value, we open a key cursor in reverse // order and get only the first pointed value. - let request = store.openCursor(null, Ci.nsIIDBCursor.PREV); + let request = store.openCursor(null, PREV); request.onsuccess = function onsuccess(event) { let cursor = event.target.result; if (!cursor) { @@ -165,7 +170,7 @@ SmsDatabaseService.prototype = { * Start a new transaction. * * @param txn_type - * Type of transaction (e.g. IDBTransaction.READ_WRITE) + * Type of transaction (e.g. READ_WRITE) * @param callback * Function to call when the transaction is available. It will * be invoked with the transaction and the 'sms' object store. @@ -254,7 +259,7 @@ SmsDatabaseService.prototype = { onMessageListCreated: function onMessageListCreated(messageList, requestId) { if (DEBUG) debug("Message list created: " + messageList); let self = this; - self.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + self.newTxn(READ_ONLY, function (error, txn, store) { if (error) { gSmsRequestManager.notifyReadMessageListFailed( requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); @@ -295,7 +300,7 @@ SmsDatabaseService.prototype = { this.lastKey += 1; message.id = this.lastKey; if (DEBUG) debug("Going to store " + JSON.stringify(message)); - this.newTxn(Ci.nsIIDBTransaction.READ_WRITE, function(error, txn, store) { + this.newTxn(READ_WRITE, function(error, txn, store) { if (error) { return; } @@ -330,7 +335,7 @@ SmsDatabaseService.prototype = { getMessage: function getMessage(messageId, requestId) { if (DEBUG) debug("Retrieving message with ID " + messageId); - this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + this.newTxn(READ_ONLY, function (error, txn, store) { if (error) { if (DEBUG) debug(error); gSmsRequestManager.notifyGetSmsFailed( @@ -382,46 +387,28 @@ SmsDatabaseService.prototype = { }, deleteMessage: function deleteMessage(messageId, requestId) { + let deleted = false; let self = this; - this.newTxn(Ci.nsIIDBTransaction.READ_WRITE, function (error, txn, store) { + this.newTxn(READ_WRITE, function (error, txn, store) { if (error) { gSmsRequestManager.notifySmsDeleteFailed( requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); return; } - let request = store.delete(messageId); + let request = store.count(messageId); - request.onerror = function onerror(event) { - if (DEBUG) debug("Caught error on request ", event.target.errorCode); - //TODO look at event.target.errorCode - gSmsRequestManager.notifySmsDeleteFailed( - requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); + request.onsuccess = function onsuccess(event) { + let count = event.target.result; + if (DEBUG) debug("Count for messageId " + messageId + ": " + count); + deleted = (count == 1); + if (deleted) { + store.delete(messageId); + } }; txn.oncomplete = function oncomplete(event) { if (DEBUG) debug("Transaction " + txn + " completed."); - // Once we transaction is done, we need to check if we actually deleted - // the message. As IndexedDB does not provide the affected records info, - // we need to try to get the message from the database again to check - // that it is actually gone. - self.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { - let request = store.getAll(messageId); - request.onsuccess = function onsuccess(event) { - let deleted = (event.target.result.length == 0); - gSmsRequestManager.notifySmsDeleted(requestId, deleted); - }; - request.onerror = function onerror(event) { - if (DEBUG) { - debug("Error checking the message deletion " + - event.target.errorCode); - } - //TODO should we notify here as an internal error? The failed check - // does not mean that the deletion has failed, so maybe we - // should notify successfully. - gSmsRequestManager.notifySmsDeleteFailed( - requestId, Ci.nsISmsRequestManager.INTERNAL_ERROR); - }; - }); + gSmsRequestManager.notifySmsDeleted(requestId, deleted); }; txn.onerror = function onerror(event) { @@ -478,7 +465,7 @@ SmsDatabaseService.prototype = { }; let self = this; - this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + this.newTxn(READ_ONLY, function (error, txn, store) { if (error) { errorCb(error); return; @@ -495,7 +482,7 @@ SmsDatabaseService.prototype = { } else if (filter.endDate != null) { timeKeyRange = IDBKeyRange.upperBound(filter.endDate.getTime()); } - let direction = reverse ? Ci.nsIIDBCursor.PREV : Ci.nsIIDBCursor.NEXT; + let direction = reverse ? PREV : NEXT; let timeRequest = store.index("timestamp").openKeyCursor(timeKeyRange, direction); @@ -573,7 +560,7 @@ SmsDatabaseService.prototype = { gSmsRequestManager.notifyNoMessageInList(requestId); return; } - this.newTxn(Ci.nsIIDBTransaction.READ_ONLY, function (error, txn, store) { + this.newTxn(READ_ONLY, function (error, txn, store) { if (DEBUG) debug("Fetching message " + messageId); let request = store.get(messageId); let message; diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js index 90ad95d190fe..85b0ad8c699d 100644 --- a/dom/system/gonk/RadioInterfaceLayer.js +++ b/dom/system/gonk/RadioInterfaceLayer.js @@ -408,13 +408,13 @@ RadioInterfaceLayer.prototype = { handleSmsReceived: function handleSmsReceived(message) { debug("handleSmsReceived: " + JSON.stringify(message)); let id = gSmsDatabaseService.saveReceivedMessage(message.sender || null, - message.body || null, + message.fullBody || null, message.timestamp); let sms = gSmsService.createSmsMessage(id, DOM_SMS_DELIVERY_RECEIVED, message.sender || null, message.receiver || null, - message.body || null, + message.fullBody || null, message.timestamp); Services.obs.notifyObservers(sms, kSmsReceivedObserverTopic, null); }, @@ -422,12 +422,14 @@ RadioInterfaceLayer.prototype = { handleSmsSent: function handleSmsSent(message) { debug("handleSmsSent: " + JSON.stringify(message)); let timestamp = Date.now(); - let id = gSmsDatabaseService.saveSentMessage(message.number, message.body, timestamp); + let id = gSmsDatabaseService.saveSentMessage(message.number, + message.fullBody, + timestamp); let sms = gSmsService.createSmsMessage(id, DOM_SMS_DELIVERY_SENT, null, message.number, - message.body, + message.fullBody, timestamp); //TODO handle errors (bug 727319) gSmsRequestManager.notifySmsSent(message.requestId, sms); @@ -532,19 +534,387 @@ RadioInterfaceLayer.prototype = { gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force); }, + /** + * List of tuples of national language identifier pairs. + * + * TODO: Support static/runtime settings, see bug 733331. + */ + enabledGsmTableTuples: [ + [RIL.PDU_NL_IDENTIFIER_DEFAULT, RIL.PDU_NL_IDENTIFIER_DEFAULT], + ], + + /** + * Use 16-bit reference number for concatenated outgoint messages. + * + * TODO: Support static/runtime settings, see bug 733331. + */ + segmentRef16Bit: false, + + /** + * Get valid SMS concatenation reference number. + */ + _segmentRef: 0, + get nextSegmentRef() { + let ref = this._segmentRef++; + + this._segmentRef %= (this.segmentRef16Bit ? 65535 : 255); + + // 0 is not a valid SMS concatenation reference number. + return ref + 1; + }, + + /** + * Calculate encoded length using specified locking/single shift table + * + * @param message + * message string to be encoded. + * @param langTable + * locking shift table string. + * @param langShiftTable + * single shift table string. + * + * @return encoded length in septets. + * + * @note that the algorithm used in this function must match exactly with + * GsmPDUHelper#writeStringAsSeptets. + */ + _countGsm7BitSeptets: function _countGsm7BitSeptets(message, langTable, langShiftTable) { + let length = 0; + for (let msgIndex = 0; msgIndex < message.length; msgIndex++) { + let septet = langTable.indexOf(message.charAt(msgIndex)); + + // According to 3GPP TS 23.038, section 6.1.1 General notes, "The + // characters marked '1)' are not used but are displayed as a space." + if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) { + continue; + } + + if (septet >= 0) { + length++; + continue; + } + + septet = langShiftTable.indexOf(message.charAt(msgIndex)); + if (septet < 0) { + return -1; + } + + // According to 3GPP TS 23.038 B.2, "This code represents a control + // character and therefore must not be used for language specific + // characters." + if (septet == RIL.PDU_NL_RESERVED_CONTROL) { + continue; + } + + // The character is not found in locking shfit table, but could be + // encoded as with single shift table. Note that it's + // still possible for septet to has the value of PDU_NL_EXTENDED_ESCAPE, + // but we can display it as a space in this case as said in previous + // comment. + length += 2; + } + + return length; + }, + + /** + * Calculate user data length of specified message string encoded in GSM 7Bit + * alphabets. + * + * @param message + * a message string to be encoded. + * + * @return null or an options object with attributes `dcs`, + * `userDataHeaderLength`, `encodedFullBodyLength`, `langIndex`, + * `langShiftIndex`, `segmentMaxSeq` set. + * + * @see #_calculateUserDataLength(). + */ + _calculateUserDataLength7Bit: function _calculateUserDataLength7Bit(message) { + let options = null; + let minUserDataSeptets = Number.MAX_VALUE; + for (let i = 0; i < this.enabledGsmTableTuples.length; i++) { + let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i]; + + const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; + const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; + + let bodySeptets = this._countGsm7BitSeptets(message, + langTable, + langShiftTable); + if (bodySeptets < 0) { + continue; + } + + let headerLen = 0; + if (langIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) { + headerLen += 3; // IEI + len + langIndex + } + if (langShiftIndex != RIL.PDU_NL_IDENTIFIER_DEFAULT) { + headerLen += 3; // IEI + len + langShiftIndex + } + + // Calculate full user data length, note the extra byte is for header len + let headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7); + let userDataSeptets = bodySeptets + headerSeptets; + let segments = bodySeptets ? 1 : 0; + if (userDataSeptets > RIL.PDU_MAX_USER_DATA_7BIT) { + if (this.segmentRef16Bit) { + headerLen += 6; + } else { + headerLen += 5; + } + + headerSeptets = Math.ceil((headerLen + 1) * 8 / 7); + let segmentSeptets = RIL.PDU_MAX_USER_DATA_7BIT - headerSeptets; + segments = Math.ceil(bodySeptets / segmentSeptets); + userDataSeptets = bodySeptets + headerSeptets * segments; + } + + if (userDataSeptets >= minUserDataSeptets) { + continue; + } + + minUserDataSeptets = userDataSeptets; + + options = { + dcs: RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET, + encodedFullBodyLength: bodySeptets, + userDataHeaderLength: headerLen, + langIndex: langIndex, + langShiftIndex: langShiftIndex, + segmentMaxSeq: segments, + }; + } + + return options; + }, + + /** + * Calculate user data length of specified message string encoded in UCS2. + * + * @param message + * a message string to be encoded. + * + * @return an options object with attributes `dcs`, `userDataHeaderLength`, + * `encodedFullBodyLength`, `segmentMaxSeq` set. + * + * @see #_calculateUserDataLength(). + */ + _calculateUserDataLengthUCS2: function _calculateUserDataLengthUCS2(message) { + let bodyChars = message.length; + let headerLen = 0; + let headerChars = Math.ceil((headerLen ? headerLen + 1 : 0) / 2); + let segments = bodyChars ? 1 : 0; + if ((bodyChars + headerChars) > RIL.PDU_MAX_USER_DATA_UCS2) { + if (this.segmentRef16Bit) { + headerLen += 6; + } else { + headerLen += 5; + } + + headerChars = Math.ceil((headerLen + 1) / 2); + let segmentChars = RIL.PDU_MAX_USER_DATA_UCS2 - headerChars; + segments = Math.ceil(bodyChars / segmentChars); + } + + return { + dcs: RIL.PDU_DCS_MSG_CODING_16BITS_ALPHABET, + encodedFullBodyLength: bodyChars * 2, + userDataHeaderLength: headerLen, + segmentMaxSeq: segments, + }; + }, + + /** + * Calculate user data length and its encoding. + * + * @param message + * a message string to be encoded. + * + * @return an options object with some or all of following attributes set: + * + * @param dcs + * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET + * constants. + * @param fullBody + * Original unfragmented text message. + * @param userDataHeaderLength + * Length of embedded user data header, in bytes. The whole header + * size will be userDataHeaderLength + 1; 0 for no header. + * @param encodedFullBodyLength + * Length of the message body when encoded with the given DCS. For + * UCS2, in bytes; for 7-bit, in septets. + * @param langIndex + * Table index used for normal 7-bit encoded character lookup. + * @param langShiftIndex + * Table index used for escaped 7-bit encoded character lookup. + * @param segmentMaxSeq + * Max sequence number of a multi-part messages, or 1 for single one. + * This number might not be accurate for a multi-part message until + * it's processed by #_fragmentText() again. + */ + _calculateUserDataLength: function _calculateUserDataLength(message) { + let options = this._calculateUserDataLength7Bit(message); + if (!options) { + options = this._calculateUserDataLengthUCS2(message); + } + + if (options) { + options.fullBody = message; + } + + debug("_calculateUserDataLength: " + JSON.stringify(options)); + return options; + }, + + /** + * Fragment GSM 7-Bit encodable string for transmission. + * + * @param text + * text string to be fragmented. + * @param langTable + * locking shift table string. + * @param langShiftTable + * single shift table string. + * @param headerLen + * Length of prepended user data header. + * + * @return an array of objects. See #_fragmentText() for detailed definition. + */ + _fragmentText7Bit: function _fragmentText7Bit(text, langTable, langShiftTable, headerLen) { + const headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7); + const segmentSeptets = RIL.PDU_MAX_USER_DATA_7BIT - headerSeptets; + let ret = []; + let begin = 0, len = 0; + for (let i = 0, inc = 0; i < text.length; i++) { + let septet = langTable.indexOf(text.charAt(i)); + if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) { + continue; + } + + if (septet >= 0) { + inc = 1; + } else { + septet = langShiftTable.indexOf(text.charAt(i)); + if (septet < 0) { + throw new Error("Given text cannot be encoded with GSM 7-bit Alphabet!"); + } + + if (septet == RIL.PDU_NL_RESERVED_CONTROL) { + continue; + } + + inc = 2; + } + + if ((len + inc) > segmentSeptets) { + ret.push({ + body: text.substring(begin, i), + encodedBodyLength: len, + }); + begin = i; + len = 0; + } + + len += inc; + } + + if (len) { + ret.push({ + body: text.substring(begin), + encodedBodyLength: len, + }); + } + + return ret; + }, + + /** + * Fragment UCS2 encodable string for transmission. + * + * @param text + * text string to be fragmented. + * @param headerLen + * Length of prepended user data header. + * + * @return an array of objects. See #_fragmentText() for detailed definition. + */ + _fragmentTextUCS2: function _fragmentTextUCS2(text, headerLen) { + const headerChars = Math.ceil((headerLen ? headerLen + 1 : 0) / 2); + const segmentChars = RIL.PDU_MAX_USER_DATA_UCS2 - headerChars; + let ret = []; + for (let offset = 0; offset < text.length; offset += segmentChars) { + let str = text.substr(offset, segmentChars); + ret.push({ + body: str, + encodedBodyLength: str.length * 2, + }); + } + + return ret; + }, + + /** + * Fragment string for transmission. + * + * Fragment input text string into an array of objects that contains + * attributes `body`, substring for this segment, `encodedBodyLength`, + * length of the encoded segment body in septets. + * + * @param text + * Text string to be fragmented. + * @param options + * Optional pre-calculated option object. The output array will be + * stored at options.segments if there are multiple segments. + * + * @return Populated options object. + */ + _fragmentText: function _fragmentText(text, options) { + if (!options) { + options = this._calculateUserDataLength(text); + } + + if (options.segmentMaxSeq <= 1) { + options.segments = null; + return options; + } + + if (options.dcs == RIL.PDU_DCS_MSG_CODING_7BITS_ALPHABET) { + const langTable = RIL.PDU_NL_LOCKING_SHIFT_TABLES[options.langIndex]; + const langShiftTable = RIL.PDU_NL_SINGLE_SHIFT_TABLES[options.langShiftIndex]; + options.segments = this._fragmentText7Bit(options.fullBody, + langTable, langShiftTable, + options.userDataHeaderLength); + } else { + options.segments = this._fragmentTextUCS2(options.fullBody, + options.userDataHeaderLength); + } + + // Re-sync options.segmentMaxSeq with actual length of returning array. + options.segmentMaxSeq = options.segments.length; + + return options; + }, + getNumberOfMessagesForText: function getNumberOfMessagesForText(text) { - //TODO: this assumes 7bit encoding, which is incorrect. Need to look - // for characters not supported by 7bit alphabets and then calculate - // length in UCS2 encoding. - return Math.ceil(text.length / 160); + return this._fragmentText(text).segmentMaxSeq; }, sendSMS: function sendSMS(number, message, requestId, processId) { - this.worker.postMessage({type: "sendSMS", - number: number, - body: message, - requestId: requestId, - processId: processId}); + let options = this._calculateUserDataLength(message); + options.type = "sendSMS"; + options.number = number; + options.requestId = requestId; + options.processId = processId; + + this._fragmentText(message, options); + if (options.segmentMaxSeq > 1) { + options.segmentRef16Bit = this.segmentRef16Bit; + options.segmentRef = this.nextSegmentRef; + } + + this.worker.postMessage(options); }, _callbacks: null, diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js index b4226827eb85..31a9ba3c438b 100644 --- a/dom/system/gonk/ril_consts.js +++ b/dom/system/gonk/ril_consts.js @@ -413,8 +413,12 @@ const PDU_MTI_SMS_STATUS_COMMAND = 0x02; const PDU_MTI_SMS_SUBMIT = 0x01; const PDU_MTI_SMS_DELIVER = 0x00; -// User Data max length in octets +// User Data max length in septets const PDU_MAX_USER_DATA_7BIT = 160; +// User Data max length in octets +const PDU_MAX_USER_DATA_8BIT = 140; +// User Data max length in chars +const PDU_MAX_USER_DATA_UCS2 = 70; // DCS - Data Coding Scheme const PDU_DCS_MSG_CODING_7BITS_ALPHABET = 0x00; diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js index 4d0f0c55852e..29bfad85f340 100644 --- a/dom/system/gonk/ril_worker.js +++ b/dom/system/gonk/ril_worker.js @@ -604,6 +604,13 @@ let RIL = { */ currentDataCalls: {}, + /** + * Hash map for received multipart sms fragments. Messages are hashed with + * its sender address and concatenation reference number. Three additional + * attributes `segmentMaxSeq`, `receivedSegments`, `segments` are inserted. + */ + _receivedSmsSegmentsMap: {}, + /** * Mute or unmute the radio. */ @@ -981,9 +988,18 @@ let RIL = { options.SMSC = this.SMSC; //TODO: verify values on 'options' - //TODO: the data encoding and length in octets should eventually be - // computed on the mainthread and passed down to us. - GsmPDUHelper.calculateUserDataLength(options); + + if (options.segmentMaxSeq > 1) { + if (!options.segmentSeq) { + // Fist segment to send + options.segmentSeq = 1; + options.body = options.segments[0].body; + options.encodedBodyLength = options.segments[0].encodedBodyLength; + } + } else { + options.body = options.fullBody; + options.encodedBodyLength = options.encodedFullBodyLength; + } Buf.newParcel(REQUEST_SEND_SMS, options); Buf.writeUint32(2); @@ -1369,6 +1385,59 @@ let RIL = { this.muted = Object.getOwnPropertyNames(this.currentCalls).length == 0; }, + /** + * Helper for processing received multipart SMS. + * + * @return null for handled segments, and an object containing full message + * body once all segments are received. + */ + _processReceivedSmsSegment: function _processReceivedSmsSegment(original) { + let hash = original.sender + ":" + original.header.segmentRef; + let seq = original.header.segmentSeq; + + let options = this._receivedSmsSegmentsMap[hash]; + if (!options) { + options = original; + this._receivedSmsSegmentsMap[hash] = options; + + options.segmentMaxSeq = original.header.segmentMaxSeq; + options.receivedSegments = 0; + options.segments = []; + } else if (options.segments[seq]) { + // Duplicated segment? + if (DEBUG) { + debug("Got duplicated segment no." + seq + " of a multipart SMS: " + + JSON.stringify(original)); + } + return null; + } + + options.segments[seq] = original.body; + options.receivedSegments++; + if (options.receivedSegments < options.segmentMaxSeq) { + if (DEBUG) { + debug("Got segment no." + seq + " of a multipart SMS: " + + JSON.stringify(options)); + } + return null; + } + + // Remove from map + delete this._receivedSmsSegmentsMap[hash]; + + // Rebuild full body + options.fullBody = ""; + for (let i = 1; i <= options.segmentMaxSeq; i++) { + options.fullBody += options.segments[i]; + } + + if (DEBUG) { + debug("Got full multipart SMS: " + JSON.stringify(options)); + } + + return options; + }, + _handleChangedCallState: function _handleChangedCallState(changedCall) { let message = {type: "callStateChange", call: {callIndex: changedCall.callIndex, @@ -1658,6 +1727,20 @@ RIL[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) { options.messageRef = Buf.readUint32(); options.ackPDU = Buf.readString(); options.errorCode = Buf.readUint32(); + + //TODO handle errors (bug 727319) + if ((options.segmentMaxSeq > 1) + && (options.segmentSeq < options.segmentMaxSeq)) { + // Setup attributes for sending next segment + let next = options.segmentSeq; + options.body = options.segments[next].body; + options.encodedBodyLength = options.segments[next].encodedBodyLength; + options.segmentSeq = next + 1; + + this.sendSMS(options); + return; + } + options.type = "sms-sent"; this.sendDOMMessage(options); }; @@ -1932,8 +2015,16 @@ RIL[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length } } - message.type = "sms-received"; - this.sendDOMMessage(message); + if (message.header && (message.header.segmentMaxSeq > 1)) { + message = this._processReceivedSmsSegment(message); + } else { + message.fullBody = message.body; + } + + if (message) { + message.type = "sms-received"; + this.sendDOMMessage(message); + } //TODO: this might be a lie? do we want to wait for the mainthread to // report back? @@ -2042,13 +2133,6 @@ RIL[UNSOLICITED_RESEND_INCALL_MUTE] = null; */ let GsmPDUHelper = { - /** - * List of tuples of national language identifier pairs. - */ - enabledGsmTableTuples: [ - [PDU_NL_IDENTIFIER_DEFAULT, PDU_NL_IDENTIFIER_DEFAULT], - ], - /** * Read one character (2 bytes) from a RIL string and decode as hex. * @@ -2369,137 +2453,6 @@ let GsmPDUHelper = { } }, - /** - * Calculate encoded length using specified locking/single shift table - * - * @param message - * message string to be encoded. - * @param langTable - * locking shift table string. - * @param langShiftTable - * single shift table string. - * - * @return encoded length in septets. - * - * @note that the algorithm used in this function must match exactly with - * #writeStringAsSeptets. - */ - _calculateLangEncodedSeptets: function _calculateLangEncodedSeptets(message, langTable, langShiftTable) { - let length = 0; - for (let msgIndex = 0; msgIndex < message.length; msgIndex++) { - let septet = langTable.indexOf(message.charAt(msgIndex)); - - // According to 3GPP TS 23.038, section 6.1.1 General notes, "The - // characters marked '1)' are not used but are displayed as a space." - if (septet == PDU_NL_EXTENDED_ESCAPE) { - continue; - } - - if (septet >= 0) { - length++; - continue; - } - - septet = langShiftTable.indexOf(message.charAt(msgIndex)); - if (septet == -1) { - return -1; - } - - // According to 3GPP TS 23.038 B.2, "This code represents a control - // character and therefore must not be used for language specific - // characters." - if (septet == PDU_NL_RESERVED_CONTROL) { - continue; - } - - // The character is not found in locking shfit table, but could be - // encoded as with single shift table. Note that it's - // still possible for septet to has the value of PDU_NL_EXTENDED_ESCAPE, - // but we can display it as a space in this case as said in previous - // comment. - length += 2; - } - - return length; - }, - - /** - * Calculate user data length and its encoding. - * - * The `options` parameter object should contain the `body` attribute, and - * the `dcs`, `userDataHeaderLength`, `encodedBodyLength`, `langIndex`, - * `langShiftIndex` attributes will be set as return: - * - * @param body - * String containing the message body. - * @param dcs - * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET - * constants. - * @param userDataHeaderLength - * Length of embedded user data header, in bytes. The whole header - * size will be userDataHeaderLength + 1; 0 for no header. - * @param encodedBodyLength - * Length of the message body when encoded with the given DCS. For - * UCS2, in bytes; for 7-bit, in septets. - * @param langIndex - * Table index used for normal 7-bit encoded character lookup. - * @param langShiftIndex - * Table index used for escaped 7-bit encoded character lookup. - */ - calculateUserDataLength: function calculateUserDataLength(options) { - //TODO: support multipart SMS, see bug 712933 - options.dcs = PDU_DCS_MSG_CODING_7BITS_ALPHABET; - options.langIndex = PDU_NL_IDENTIFIER_DEFAULT; - options.langShiftIndex = PDU_NL_IDENTIFIER_DEFAULT; - options.encodedBodyLength = 0; - options.userDataHeaderLength = 0; - - let needUCS2 = true; - let minUserDataSeptets = Number.MAX_VALUE; - for (let i = 0; i < this.enabledGsmTableTuples.length; i++) { - let [langIndex, langShiftIndex] = this.enabledGsmTableTuples[i]; - - const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; - const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; - - let bodySeptets = this._calculateLangEncodedSeptets(options.body, - langTable, - langShiftTable); - if (bodySeptets < 0) { - continue; - } - - let headerLen = 0; - if (langIndex != PDU_NL_IDENTIFIER_DEFAULT) { - headerLen += 3; // IEI + len + langIndex - } - if (langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { - headerLen += 3; // IEI + len + langShiftIndex - } - - // Calculate full user data length, note the extra byte is for header len - let headerSeptets = Math.ceil((headerLen ? headerLen + 1 : 0) * 8 / 7); - let userDataSeptets = bodySeptets + headerSeptets; - if (userDataSeptets >= minUserDataSeptets) { - continue; - } - - needUCS2 = false; - minUserDataSeptets = userDataSeptets; - - options.encodedBodyLength = bodySeptets; - options.userDataHeaderLength = headerLen; - options.langIndex = langIndex; - options.langShiftIndex = langShiftIndex; - } - - if (needUCS2) { - options.dcs = PDU_DCS_MSG_CODING_16BITS_ALPHABET; - options.encodedBodyLength = options.body.length * 2; - options.userDataHeaderLength = 0; - } - }, - /** * Read 1 + UDHL octets and construct user data header at return. * @@ -2528,6 +2481,30 @@ let GsmPDUHelper = { dataAvailable -= 2; switch (id) { + case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { + let ref = this.readHexOctet(); + let max = this.readHexOctet(); + let seq = this.readHexOctet(); + dataAvailable -= 3; + if (max && seq && (seq <= max)) { + header.segmentRef = ref; + header.segmentMaxSeq = max; + header.segmentSeq = seq; + } + break; + } + case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { + let ref = (this.readHexOctet() << 8) | this.readHexOctet(); + let max = this.readHexOctet(); + let seq = this.readHexOctet(); + dataAvailable -= 4; + if (max && seq && (seq <= max)) { + header.segmentRef = ref; + header.segmentMaxSeq = max; + header.segmentSeq = seq; + } + break; + } case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: let langShiftIndex = this.readHexOctet(); --dataAvailable; @@ -2582,6 +2559,20 @@ let GsmPDUHelper = { writeUserDataHeader: function writeUserDataHeader(options) { this.writeHexOctet(options.userDataHeaderLength); + if (options.segmentMaxSeq > 1) { + if (options.segmentRef16Bit) { + this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT); + this.writeHexOctet(4); + this.writeHexOctet((options.segmentRef >> 8) & 0xFF); + } else { + this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT); + this.writeHexOctet(3); + } + this.writeHexOctet(options.segmentRef & 0xFF); + this.writeHexOctet(options.segmentMaxSeq & 0xFF); + this.writeHexOctet(options.segmentSeq & 0xFF); + } + if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); this.writeHexOctet(1); @@ -2598,8 +2589,17 @@ let GsmPDUHelper = { /** * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit * (UCS2) data. + * + * @param msg + * message object for output. + * @param length + * length of user data to read in octets. + * @param codingScheme + * coding scheme used to decode user data. + * @param hasHeader + * whether a header is embedded. */ - readUserData: function readUserData(length, codingScheme, hasHeader) { + readUserData: function readUserData(msg, length, codingScheme, hasHeader) { if (DEBUG) { debug("Reading " + length + " bytes of user data."); debug("Coding scheme: " + codingScheme); @@ -2636,43 +2636,45 @@ let GsmPDUHelper = { break; } - let header; + if (DEBUG) debug("PDU: message encoding is " + encoding + " bit."); + let paddingBits = 0; if (hasHeader) { - header = this.readUserDataHeader(); + msg.header = this.readUserDataHeader(); if (encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { - let headerBits = (header.length + 1) * 8; + let headerBits = (msg.header.length + 1) * 8; let headerSeptets = Math.ceil(headerBits / 7); length -= headerSeptets; paddingBits = headerSeptets * 7 - headerBits; } else { - length -= (header.length + 1); + length -= (msg.header.length + 1); } } - if (DEBUG) debug("PDU: message encoding is " + encoding + " bit."); + msg.body = null; switch (encoding) { case PDU_DCS_MSG_CODING_7BITS_ALPHABET: // 7 bit encoding allows 140 octets, which means 160 characters // ((140x8) / 7 = 160 chars) if (length > PDU_MAX_USER_DATA_7BIT) { if (DEBUG) debug("PDU error: user data is too long: " + length); - return null; + break; } - return this.readSeptetsToString(length, - paddingBits, - hasHeader ? header.langIndex : PDU_NL_IDENTIFIER_DEFAULT, - hasHeader ? header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT); + let langIndex = hasHeader ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT; + let langShiftIndex = hasHeader ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT; + msg.body = this.readSeptetsToString(length, paddingBits, langIndex, + langShiftIndex); + break; case PDU_DCS_MSG_CODING_8BITS_ALPHABET: // Unsupported. - return null; + break; case PDU_DCS_MSG_CODING_16BITS_ALPHABET: - return this.readUCS2String(length); + msg.body = this.readUCS2String(length); + break; } - return null; }, /** @@ -2786,9 +2788,8 @@ let GsmPDUHelper = { // - TP-User-Data - if (userDataLength > 0) { - msg.body = this.readUserData(userDataLength, - dataCodingScheme, - hasUserDataHeader); + this.readUserData(msg, userDataLength, dataCodingScheme, + hasUserDataHeader); } return msg; diff --git a/dom/system/gonk/tests/header_helpers.js b/dom/system/gonk/tests/header_helpers.js index b6b0ae549a7d..c056e565b850 100644 --- a/dom/system/gonk/tests/header_helpers.js +++ b/dom/system/gonk/tests/header_helpers.js @@ -119,6 +119,20 @@ function newIncomingParcel(fakeParcelSize, response, request, data) { return bytes; } +/** + * + */ +function newRadioInterfaceLayer() { + let ril_ns = { + ChromeWorker: function ChromeWorker() { + // Stub function + }, + }; + + subscriptLoader.loadSubScript("resource://gre/components/RadioInterfaceLayer.js", ril_ns); + return new ril_ns.RadioInterfaceLayer(); +} + /** * Test whether specified function throws exception with expected * result. diff --git a/dom/system/gonk/tests/test_ril_worker_sms.js b/dom/system/gonk/tests/test_ril_worker_sms.js index d08876cad8a3..53c0196f2005 100644 --- a/dom/system/gonk/tests/test_ril_worker_sms.js +++ b/dom/system/gonk/tests/test_ril_worker_sms.js @@ -55,10 +55,12 @@ add_test(function test_nl_single_shift_tables_validity() { }); /** - * Verify GsmPDUHelper#_calculateLangEncodedSeptets() and + * Verify RadioInterfaceLayer#_countGsm7BitSeptets() and * GsmPDUHelper#writeStringAsSeptets() algorithm match each other. */ -add_test(function test_GsmPDUHelper__calculateLangEncodedSeptets() { +add_test(function test_RadioInterfaceLayer__countGsm7BitSeptets() { + let ril = newRadioInterfaceLayer(); + let worker = newWorker({ postRILMessage: function fakePostRILMessage(data) { // Do nothing @@ -78,9 +80,9 @@ add_test(function test_GsmPDUHelper__calculateLangEncodedSeptets() { function do_check_calc(str, expectedCalcLen, lst, sst) { do_check_eq(expectedCalcLen, - helper._calculateLangEncodedSeptets(str, - PDU_NL_LOCKING_SHIFT_TABLES[lst], - PDU_NL_SINGLE_SHIFT_TABLES[sst])); + ril._countGsm7BitSeptets(str, + PDU_NL_LOCKING_SHIFT_TABLES[lst], + PDU_NL_SINGLE_SHIFT_TABLES[sst])); helper.resetOctetWritten(); helper.writeStringAsSeptets(str, 0, lst, sst); @@ -131,28 +133,18 @@ add_test(function test_GsmPDUHelper__calculateLangEncodedSeptets() { }); /** - * Verify GsmPDUHelper#calculateUserDataLength handles national language + * Verify RadioInterfaceLayer#calculateUserDataLength handles national language * selection correctly. */ -add_test(function test_GsmPDUHelper_calculateUserDataLength() { - let worker = newWorker({ - postRILMessage: function fakePostRILMessage(data) { - // Do nothing - }, - postMessage: function fakePostMessage(message) { - // Do nothing - } - }); +add_test(function test_RadioInterfaceLayer__calculateUserDataLength() { + let ril = newRadioInterfaceLayer(); - let helper = worker.GsmPDUHelper; - let calc = helper.calculateUserDataLength; function test_calc(str, expected, enabledGsmTableTuples) { - helper.enabledGsmTableTuples = enabledGsmTableTuples; - let options = {body: str}; - calc.call(helper, options); + ril.enabledGsmTableTuples = enabledGsmTableTuples; + let options = ril._calculateUserDataLength(str); do_check_eq(expected[0], options.dcs); - do_check_eq(expected[1], options.encodedBodyLength); + do_check_eq(expected[1], options.encodedFullBodyLength); do_check_eq(expected[2], options.userDataHeaderLength); do_check_eq(expected[3], options.langIndex); do_check_eq(expected[4], options.langShiftIndex); @@ -160,9 +152,9 @@ add_test(function test_GsmPDUHelper_calculateUserDataLength() { // Test UCS fallback // - No any default enabled nl tables - test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0, 0, 0], []); + test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0,], []); // - Character not defined in enabled nl tables - test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0, 0, 0], [[2, 2]]); + test_calc("A", [PDU_DCS_MSG_CODING_16BITS_ALPHABET, 2, 0,], [[2, 2]]); // With GSM default nl tables test_calc("A", [PDU_DCS_MSG_CODING_7BITS_ALPHABET, 1, 0, 0, 0], [[0, 0]]); @@ -211,6 +203,101 @@ add_test(function test_GsmPDUHelper_calculateUserDataLength() { run_next_test(); }); +function generateStringOfLength(str, length) { + while (str.length < length) { + if (str.length < (length / 2)) { + str = str + str; + } else { + str = str + str.substr(0, length - str.length); + } + } + + return str; +} + +/** + * Verify RadioInterfaceLayer#_calculateUserDataLength7Bit multipart handling. + */ +add_test(function test_RadioInterfaceLayer__calculateUserDataLength7Bit_multipart() { + let ril = newRadioInterfaceLayer(); + + function test_calc(str, expected) { + let options = ril._calculateUserDataLength7Bit(str); + + do_check_eq(expected[0], options.encodedFullBodyLength); + do_check_eq(expected[1], options.userDataHeaderLength); + do_check_eq(expected[2], options.segmentMaxSeq); + } + + test_calc(generateStringOfLength("A", PDU_MAX_USER_DATA_7BIT), + [PDU_MAX_USER_DATA_7BIT, 0, 1]); + test_calc(generateStringOfLength("A", PDU_MAX_USER_DATA_7BIT + 1), + [PDU_MAX_USER_DATA_7BIT + 1, 5, 2]); + + run_next_test(); +}); + +/** + * Verify RadioInterfaceLayer#_fragmentText7Bit(). + */ +add_test(function test_RadioInterfaceLayer__fragmentText7Bit() { + let ril = newRadioInterfaceLayer(); + + function test_calc(str, expected) { + let options = ril._fragmentText(str); + if (expected) { + do_check_eq(expected, options.segments.length); + } else { + do_check_eq(null, options.segments); + } + } + + // Boundary checks + test_calc(""); + test_calc(generateStringOfLength("A", PDU_MAX_USER_DATA_7BIT)); + test_calc(generateStringOfLength("A", PDU_MAX_USER_DATA_7BIT + 1), 2); + + // Escaped character + test_calc(generateStringOfLength("{", PDU_MAX_USER_DATA_7BIT / 2)); + test_calc(generateStringOfLength("{", PDU_MAX_USER_DATA_7BIT / 2 + 1), 2); + // Escaped character cannot be separated + test_calc(generateStringOfLength("{", (PDU_MAX_USER_DATA_7BIT - 7) * 2 / 2), 3); + + // Test headerLen, 7 = Math.ceil(6 * 8 / 7), 6 = headerLen + 1 + test_calc(generateStringOfLength("A", PDU_MAX_USER_DATA_7BIT - 7)); + test_calc(generateStringOfLength("A", (PDU_MAX_USER_DATA_7BIT - 7) * 2), 2); + test_calc(generateStringOfLength("A", (PDU_MAX_USER_DATA_7BIT - 7) * 3), 3); + + run_next_test(); +}); + +/** + * Verify RadioInterfaceLayer#_fragmentTextUCS2(). + */ +add_test(function test_RadioInterfaceLayer__fragmentTextUCS2() { + let ril = newRadioInterfaceLayer(); + + function test_calc(str, expected) { + let options = ril._fragmentText(str); + if (expected) { + do_check_eq(expected, options.segments.length); + } else { + do_check_eq(null, options.segments); + } + } + + // Boundary checks + test_calc(generateStringOfLength("\ua2db", PDU_MAX_USER_DATA_UCS2)); + test_calc(generateStringOfLength("\ua2db", PDU_MAX_USER_DATA_UCS2 + 1), 2); + + // UCS2 character cannot be separated + ril.segmentRef16Bit = true; + test_calc(generateStringOfLength("\ua2db", (PDU_MAX_USER_DATA_UCS2 * 2 - 7) * 2 / 2), 3); + ril.segmentRef16Bit = false; + + run_next_test(); +}); + /** * Verify GsmPDUHelper#writeStringAsSeptets() padding bits handling. */ @@ -386,6 +473,8 @@ function add_test_receiving_sms(expected, pdu) { } function test_receiving_7bit_alphabets(lst, sst) { + let ril = newRadioInterfaceLayer(); + let worker = newWriteHexOctetAsUint8Worker(); let helper = worker.GsmPDUHelper; let buf = worker.Buf; @@ -405,8 +494,7 @@ function test_receiving_7bit_alphabets(lst, sst) { for (let i = 0; i < text.length;) { let len = Math.min(70, text.length - i); let expected = text.substring(i, i + len); - let septets = helper._calculateLangEncodedSeptets(expected, langTable, - langShiftTable); + let septets = ril._countGsm7BitSeptets(expected, langTable, langShiftTable); let rawBytes = get7bitRawBytes(expected); let pdu = compose7bitPdu(lst, sst, rawBytes, septets); add_test_receiving_sms(expected, pdu); diff --git a/dom/telephony/TelephonyCall.cpp b/dom/telephony/TelephonyCall.cpp index 1792bd5cae71..65fc462cd784 100644 --- a/dom/telephony/TelephonyCall.cpp +++ b/dom/telephony/TelephonyCall.cpp @@ -79,7 +79,7 @@ TelephonyCall::ChangeStateInternal(PRUint16 aCallState, bool aFireEvents) stateString.AssignLiteral("dialing"); break; case nsIRadioInterfaceLayer::CALL_STATE_ALERTING: - stateString.AssignLiteral("ringing"); + stateString.AssignLiteral("alerting"); break; case nsIRadioInterfaceLayer::CALL_STATE_BUSY: stateString.AssignLiteral("busy"); @@ -158,7 +158,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TelephonyCall, Telephony, "mTelephony") NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(statechange) NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(dialing) - NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(ringing) + NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(alerting) NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(busy) NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(connecting) NS_CYCLE_COLLECTION_TRAVERSE_EVENT_HANDLER(connected) @@ -172,7 +172,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TelephonyCall, NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mTelephony) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(statechange) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(dialing) - NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(ringing) + NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(alerting) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(busy) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(connecting) NS_CYCLE_COLLECTION_UNLINK_EVENT_HANDLER(connected) @@ -240,7 +240,7 @@ TelephonyCall::HangUp() NS_IMPL_EVENT_HANDLER(TelephonyCall, statechange) NS_IMPL_EVENT_HANDLER(TelephonyCall, dialing) -NS_IMPL_EVENT_HANDLER(TelephonyCall, ringing) +NS_IMPL_EVENT_HANDLER(TelephonyCall, alerting) NS_IMPL_EVENT_HANDLER(TelephonyCall, busy) NS_IMPL_EVENT_HANDLER(TelephonyCall, connecting) NS_IMPL_EVENT_HANDLER(TelephonyCall, connected) diff --git a/dom/telephony/TelephonyCall.h b/dom/telephony/TelephonyCall.h index 972e91f7edfa..32ef7d73ff65 100644 --- a/dom/telephony/TelephonyCall.h +++ b/dom/telephony/TelephonyCall.h @@ -54,7 +54,7 @@ class TelephonyCall : public nsDOMEventTargetHelper, { NS_DECL_EVENT_HANDLER(statechange) NS_DECL_EVENT_HANDLER(dialing) - NS_DECL_EVENT_HANDLER(ringing) + NS_DECL_EVENT_HANDLER(alerting) NS_DECL_EVENT_HANDLER(busy) NS_DECL_EVENT_HANDLER(connecting) NS_DECL_EVENT_HANDLER(connected) diff --git a/dom/telephony/nsIDOMTelephonyCall.idl b/dom/telephony/nsIDOMTelephonyCall.idl index a9d7a0f865f0..59c240b335ca 100644 --- a/dom/telephony/nsIDOMTelephonyCall.idl +++ b/dom/telephony/nsIDOMTelephonyCall.idl @@ -41,7 +41,7 @@ interface nsIDOMEventListener; -[scriptable, builtinclass, uuid(832b7551-ff53-403f-9e2c-d7d28e2bb40b)] +[scriptable, builtinclass, uuid(f741d52a-38bd-48f8-838b-cf4cd74a6ea5)] interface nsIDOMTelephonyCall : nsIDOMEventTarget { readonly attribute DOMString number; @@ -54,7 +54,7 @@ interface nsIDOMTelephonyCall : nsIDOMEventTarget attribute nsIDOMEventListener onstatechange; attribute nsIDOMEventListener ondialing; - attribute nsIDOMEventListener onringing; + attribute nsIDOMEventListener onalerting; attribute nsIDOMEventListener onbusy; attribute nsIDOMEventListener onconnecting; attribute nsIDOMEventListener onconnected; diff --git a/testing/jetpack/jetpack-location.txt b/testing/jetpack/jetpack-location.txt index 2fe6bbf41c23..2f4fd47b1f23 100644 --- a/testing/jetpack/jetpack-location.txt +++ b/testing/jetpack/jetpack-location.txt @@ -1 +1 @@ -http://hg.mozilla.org/projects/addon-sdk/archive/28b4f9e190e3.tar.bz2 +http://hg.mozilla.org/projects/addon-sdk/archive/3ce8a0112619.tar.bz2 diff --git a/toolkit/components/passwordmgr/test/test_bug_627616.html b/toolkit/components/passwordmgr/test/test_bug_627616.html index eaa2d8982d98..7b498bfdfac5 100644 --- a/toolkit/components/passwordmgr/test/test_bug_627616.html +++ b/toolkit/components/passwordmgr/test/test_bug_627616.html @@ -31,11 +31,13 @@ login2 = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo); login2.init("http://mochi.test:8888", null, "mochirealm", "user1name", "user1pass", "", ""); pwmgr.addLogin(login2); + startCallbackTimer(); } function cleanup() { var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); pwmgr.removeLogin(login); pwmgr.removeLogin(login2); + timer.cancel(); } function makeXHR(expectedStatus, expectedText, extra) { @@ -59,7 +61,6 @@ function testNonAnonymousCredentials() { var xhr = makeXHR(200, "OK"); xhr.send(); - startCallbackTimer(); } function testAnonymousCredentials() { @@ -77,10 +78,14 @@ xhr.send(); } + var gExpectedDialogs = 0; var gCurrentTest; function runNextTest() { + is(gExpectedDialogs, 0, "received expected number of auth dialogs"); + Cc["@mozilla.org/network/http-auth-manager;1"].getService(Components.interfaces.nsIHttpAuthManager).clearAll(); if (pendingTests.length > 0) { - gCurrentTest = pendingTests.shift(); + ({expectedDialogs: gExpectedDialogs, + test: gCurrentTest}) = pendingTests.shift(); gCurrentTest.call(this); } else { cleanup(); @@ -88,8 +93,9 @@ } } - var pendingTests = [testNonAnonymousCredentials, testAnonymousCredentials, - testAnonymousNoAuth]; + var pendingTests = [{expectedDialogs: 2, test: testNonAnonymousCredentials}, + {expectedDialogs: 1, test: testAnonymousCredentials}, + {expectedDialogs: 0, test: testAnonymousNoAuth}]; init(); runNextTest(); @@ -97,9 +103,8 @@ { var dialog = doc.getElementById("commonDialog"); dialog.acceptDialog(); - if (gCurrentTest == testNonAnonymousCredentials) { - startCallbackTimer(); - } + gExpectedDialogs--; + startCallbackTimer(); } diff --git a/xpcom/glue/pldhash.cpp b/xpcom/glue/pldhash.cpp index 0f5b1e14ee71..138953f1f490 100644 --- a/xpcom/glue/pldhash.cpp +++ b/xpcom/glue/pldhash.cpp @@ -234,13 +234,12 @@ PL_DHashTableInit(PLDHashTable *table, const PLDHashTableOps *ops, void *data, PRUint32 nbytes; #ifdef DEBUG - if (entrySize > 10 * sizeof(void *)) { + if (entrySize > 16 * sizeof(void *)) { printf_stderr( "pldhash: for the table at address %p, the given entrySize" - " of %lu %s favors chaining over double hashing.\n", + " of %lu definitely favors chaining over double hashing.\n", (void *) table, - (unsigned long) entrySize, - (entrySize > 16 * sizeof(void*)) ? "definitely" : "probably"); + (unsigned long) entrySize); } #endif