import changes from `dev' branch of rmottola/Arctic-Fox:

- Bug 1195755: Don't assert recursion depth sanity on Mac, because there is none. r=me (e25096acc1)
- Bug 1217940 - remove BindingUtils.h from CycleCollectedJSRuntime.cpp; r=mccr8 (012fad0b80)
- Bug 1118285 - The browser.newtab.url preference is abused and should be removed. (ca573649c6)
- Bug 1209591 - allow loadURI consumers to expose whether an error page was immediately loaded as result of an error, r=smaug,mak (c033d86f07)
- Bug 1167132 - Part 14: [NetworkManager] Move network information into a separate interface (NetStats). r=ethan (87acc048cc)
- Bug 1167132 - Part 15: [NetworkManager] Move network information into a separate interface (NetworkInterfaceList). r=echen (a2a96e481e)
- Bug 1205240 - Add JSON Validation code in order to prevent invalid file. r=seanlin (8c7261ba8c)
- Bug 1215429 - Add import statement in order to access file object in chrome code of TVSimulatorService. r=seanlin (5ba9e78581)
- Bug 1217093 - Remove for-each from dom/. r=smaug (5af3efbd62)
- var-let (576b2489ec)
- Bug 1183440 - Replaces Promise.defer() with the Promise constructor in push tests. r=kitcambridge (16dfaa59b3)
- Bug 1191453 - Drop subscriptions for a site when the user revokes push permissions. r=mt,MattN (5edd10e5ad)
- Bug 1159641, Part 1 - Skip the permission check in `pushManager.getSubscription()`. r=mt (d399c496d7)
- Bug 1159641, Part 2 - Use tasks in the Push permissions test. r=mt (132484c355)
- Bug 1206302 - Use DOMException for Push errors. r=mt (5a675714fa)
- Bug 1193365 - Disable push debug. r=kitcambridge (1dc20e69b0)
- Bug 1219063, Part 1 - Use transactions for updating Push subscription permissions. r=mt (8c28453942)
- Bug 1219063, Part 2 - Remove obsolete "push" permission. r=mt (84a36931cd)
- Bug 1217065 - Unconditionally ack incoming updates. r=dragana,benbangert (e0bfa4454f)
- Bug 1212593 - Fix PushService behavior when we are switching between push servers. r=kcambridge (0afa39e743)
- Bug 1206163 - Retry failed register requests on reconnect. r=dragana (6ed1258b15)
- Bug 1218591 - Reset the WebSocket retry counter when the server replies. r=dragana (64e800db60)
- Bug 1210943 - Drop subscriptions unconditionally if the UAID changes. r=benbangert (52f538a7de)
- Bug 1214366 - Part 1: Don't preprocess PushServiceWebSocket.jsm. r=kitcambridge (a78b9fc838)
- Bug 1214366 - Part 3: Use getLastVisited equivalent in PushService.jsm. r=kitcambridge,rnewman (bc7004ad32)
- Bug 1210896, Part 1 - Use Console.jsm to log Push errors. r=mt (04335cc37f)
- Bug 1216683 - For the WebSocket version unregister should return true even if we are offline. r=kitcambridge (0f6e397a03)
- Bug 1210896, Part 2 - Use JS errors to reject internal Push promises. r=mt (3546b2f7c8)
- Bug 1223481 - Use the "potentially trustworthy origin" helper to validate Push server URLs. r=dragana (0c21f551f3)
- Bug 1223202 - Only send subscription change events if the Push permission is granted. r=mt (afeaf0dceb)
- Bug 1201128 - Don't send channel IDs in the Push handshake. r=nsm (dbbadb5c16)
- var-let (a35cb6aeca)
- Bug 1210211 - Part 1: Delay updating push quota. r=kitcambridge (53f5735ff0)
- Bug 1210211 - Part 2: Notify Push service of visible notifications. r=baku (9182bcb7d1)
- Bug 1170115 - Use clear-origin-data to remove Push records. r=allstars.chh (47f1070bab)
- Bug 1211418 - Part 1: Ensure Data Consistency after Collision of SMS Segment. r=echen. (f2d5221984)
- Bug 1211418 - Part 2: Add Test Coverage for the Collision of SMS Segment. r=echen. (06f7ba7308)
- Bug 1159132 - Part 1: Use dun apn only when config ro.tethering.dun_required is set. r=echen (bbb4fd2798)
- Bug 1159132 - Part 2: Set ro.tethering.dun_required when running dun test case. r=echen (11fe9344be)
- Bug 1187262 - Let the flag 'Services.io.offline' reference the state of tethering. r=jjong (ee22fd9358)
- Bug 1148671 - ipv6 and dual stack support on Lollipop. r=hchang (a9f7dc570e)
- Bug 1173671 - just warn if we fail to remove old default routes. r=echen (b4ab24da9f)
- Bug 1175817 - [NetworkManager] remove old default routes explicitly. r=echen,smaug (3f9a0b98ab)
- Bug 1174998 - Part 1: add setMtu() support in NetworkService. r=echen,smaug (9621036470)
- Bug 1174998 - Part 2: Set MTU for connected network interfaces. r=echen (397c898942)
- Bug 1197667 - [NetworkManager] Part 1: add missing implementation for 'allNetworkInfo'. r=echen (a49fd3498b)
- Bug 1197667 - [NetworkManager] Part 2: add test case for 'allNetworkInfo'. r=echen (942a52b0d4)
- Bug 1057091 - Add USB tethring command supporting IPv6 outgoing interface. r=hchang (9210eb5a1d)
- Bug 1177236 - Usage alert doesn't work when tethering is enabled. r=ethan (4bdd8ae226)
- Bug 1168938 - Memory safety bug in NetworkUtils::postTetherInterfaceList. r=fabrice (97485ac95c)
- Bug 1138757 - Part 1: Fix the logic of checking invalid port in CDMA WAP Push. r=echen (68dac00e52)
- Bug 1138757 - Part 2: Add Test Coverage for CDMA Wap Push. r=echen (9d54278aa9)
- Bug 1209891 - Do Not Reply Read-Report if a MMS Message Was Marked from Unread to Read Multiple Times. r=echen (421550db06)
- var-let (2ed380bb64)
- bug 1175005: performance regression. backout_f081c464c1e2 (28e1ee74b9)
- Bug 1207665 - Block Intel GMA 3150 for d3d11/d2d on all drivers. (bug 1207665 part 1, r=jrmuizel). r=jrmuizel (bb8eac6fa8)
- Bug 1188105: Parse bad driver versions. r=botond (8c856cac36)
- Bug 1075089 - Move popup menu frame offset to LookAndFeel and fix default offset for OS X. r=Enn (e1f7d0c418)
- Bug 1134385. Delete main thread assertion in CompositorVsyncDispatcher. r=kats (0945e91185)
- some profiler stuff (d3d68abdad)
- Bug 1156283 - Avoid shutdown observer race when shutting down gfx on Mac. r=roc (f66195546b)
This commit is contained in:
2023-01-21 00:07:37 +08:00
parent 2234923354
commit f579c98b65
147 changed files with 3784 additions and 1679 deletions
+25 -14
View File
@@ -4630,7 +4630,10 @@ nsDocShell::LoadURIWithOptions(const char16_t* aURI,
// what happens
if (NS_ERROR_MALFORMED_URI == rv) {
DisplayLoadError(rv, uri, aURI, nullptr);
if (DisplayLoadError(rv, uri, aURI, nullptr) &&
(aLoadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
}
}
if (NS_FAILED(rv) || !uri) {
@@ -4695,8 +4698,10 @@ nsDocShell::LoadURIWithOptions(const char16_t* aURI,
NS_IMETHODIMP
nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
const char16_t* aURL,
nsIChannel* aFailedChannel)
nsIChannel* aFailedChannel,
bool* aDisplayedErrorPage)
{
*aDisplayedErrorPage = false;
// Get prompt and string bundle servcies
nsCOMPtr<nsIPrompt> prompter;
nsCOMPtr<nsIStringBundle> stringBundle;
@@ -5068,8 +5073,10 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
if (UseErrorPages()) {
// Display an error page
LoadErrorPage(aURI, aURL, errorPage.get(), error.get(),
messageStr.get(), cssClass.get(), aFailedChannel);
nsresult loadedPage = LoadErrorPage(aURI, aURL, errorPage.get(),
error.get(), messageStr.get(),
cssClass.get(), aFailedChannel);
*aDisplayedErrorPage = NS_SUCCEEDED(loadedPage);
} else {
// The prompter reqires that our private window has a document (or it
// asserts). Satisfy that assertion now since GetDoc will force
@@ -10325,7 +10332,10 @@ nsDocShell::InternalLoad2(nsIURI* aURI,
if (NS_FAILED(rv)) {
nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
DisplayLoadError(rv, aURI, nullptr, chan);
if (DisplayLoadError(rv, aURI, nullptr, chan) &&
(aFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
}
}
return rv;
@@ -11713,7 +11723,7 @@ nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI)
// should just do a spec compare, rather than two gets of the scheme and
// then the path. -Gagan
nsresult rv;
nsAutoCString buf, pref;
nsAutoCString buf;
rv = aURI->GetScheme(buf);
if (NS_FAILED(rv)) {
@@ -11731,16 +11741,17 @@ nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI)
}
}
rv = Preferences::GetDefaultCString("browser.newtab.url", &pref);
if (NS_FAILED(rv)) {
return true;
// Check if the webbrowser chrome wants us to proceed - by default it ensures
// aURI is not the newtab URI.
nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner);
if (browserChrome3) {
bool shouldAdd;
rv = browserChrome3->ShouldAddToSessionHistory(this, aURI, &shouldAdd);
NS_ENSURE_SUCCESS(rv, true);
return shouldAdd;
}
rv = aURI->GetSpec(buf);
NS_ENSURE_SUCCESS(rv, true);
return !buf.Equals(pref);
return true;
}
nsresult
+8
View File
@@ -733,6 +733,14 @@ protected:
*/
void MaybeInitTiming();
bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL,
nsIChannel* aFailedChannel)
{
bool didDisplayLoadError = false;
DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError);
return didDisplayLoadError;
}
public:
// Event type dispatched by RestorePresentation
class RestorePresentationEvent : public nsRunnable
+8 -5
View File
@@ -46,7 +46,7 @@ interface nsITabParent;
typedef unsigned long nsLoadFlags;
[scriptable, builtinclass, uuid(9f2babc4-4c2a-4cf7-929f-a1efc325b0df)]
[scriptable, builtinclass, uuid(b1df6e41-c8dd-45c2-bc18-dd330d986214)]
interface nsIDocShell : nsIDocShellTreeItem
{
/**
@@ -449,11 +449,14 @@ interface nsIDocShell : nsIDocShellTreeItem
* @param aURI nsIURI of the page where the error happened
* @param aURL wstring of the page where the error happened
* @param aFailedChannel The channel related to this error
*
* Returns whether or not we displayed an error page (note: will always
* return false if in-content error pages are disabled!)
*/
void displayLoadError(in nsresult aError,
in nsIURI aURI,
in wstring aURL,
[optional] in nsIChannel aFailedChannel);
boolean displayLoadError(in nsresult aError,
in nsIURI aURI,
in wstring aURL,
[optional] in nsIChannel aFailedChannel);
/**
* The channel that failed to load and resulted in an error page.
+7 -3
View File
@@ -16,7 +16,7 @@ interface nsIURI;
* location, stop or restart an in process load, or determine where the object
* has previously gone.
*/
[scriptable, uuid(e186891c-b053-4fe7-a268-a1c80234b8a2)]
[scriptable, uuid(3ade79d4-8cb9-4952-b18d-4f9b63ca0d31)]
interface nsIWebNavigation : nsISupports
{
/**
@@ -184,6 +184,12 @@ interface nsIWebNavigation : nsISupports
*/
const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_OWNER = 0x40000;
/**
* Overwrite the returned error code with a specific result code
* when an error page is displayed.
*/
const unsigned long LOAD_FLAGS_ERROR_LOAD_CHANGES_RV = 0x80000;
/**
* This flag specifies that the URI may be submitted to a third-party
* server for correction. This should only be applied to non-sensitive
@@ -196,8 +202,6 @@ interface nsIWebNavigation : nsISupports
*/
const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000;
/* Note that flag 0x80000 is available. */
/**
* Loads a given URI. This will give priority to loading the requested URI
* in the object implementing this interface. If it can't be loaded here
+1 -1
View File
@@ -596,7 +596,7 @@ this.expandPermissions = function expandPermissions(aPermName, aAccess) {
// Add the same suffix to each of the additions.
if (tableEntry.additional) {
for each (let additional in tableEntry.additional) {
for (let additional of tableEntry.additional) {
permArr = permArr.concat(appendAccessToPermName(additional, requestedSuffixes));
}
}
+3
View File
@@ -87,6 +87,9 @@ enum DOM4ErrorTypeCodeMap {
BtAuthFailureError = 0,
BtRmtDevDownError = 0,
BtAuthRejectedError = 0,
/* Push API errors */
PermissionDeniedError = 0,
};
#define DOM4_MSG_DEF(name, message, nsresult) {(nsresult), name, #name, message},
+6
View File
@@ -151,5 +151,11 @@ DOM4_MSG_DEF(InvalidStateError, "A mutation operation was attempted on a file st
DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to FileHandle.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR)
DOM4_MSG_DEF(QuotaExceededError, "The current file handle exceeded its quota limitations.", NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR)
/* Push API errors. */
DOM4_MSG_DEF(InvalidStateError, "Invalid service worker registration.", NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR)
DOM4_MSG_DEF(PermissionDeniedError, "User denied permission to use the Push API.", NS_ERROR_DOM_PUSH_DENIED_ERR)
DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUSH_ABORT_ERR)
DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE)
DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception")
DOM_MSG_DEF(NS_ERROR_DOM_DOMEXCEPTION, "A DOMException was thrown")
+1 -1
View File
@@ -21,7 +21,7 @@
<script type="application/javascript">
<![CDATA[
var imports = [ "SimpleTest", "is", "isnot", "ok" ];
for each (var name in imports) {
for (var name of imports) {
window[name] = window.opener.wrappedJSObject[name];
}
+1 -1
View File
@@ -13,7 +13,7 @@ function handleRequest(request, response) {
invalidHeaders.push("Authorization");
}
for each (let header in invalidHeaders) {
for (let header of invalidHeaders) {
if (request.hasHeader(header)) {
response.setStatusLine(null, 500, "Server Error");
headers[header.toLowerCase()] = request.getHeader(header);
@@ -119,7 +119,7 @@
mm.loadFrameScript("data:,(" + childFrameScript.toString() + ")();",
false);
for each (let message in messages) {
for (let message of messages) {
mm.sendAsyncMessage("test:ipcClonedMessage", message);
}
});
@@ -338,7 +338,7 @@ BrowserElementAuthPrompt.prototype = {
prompt.authInfo.password = password;
}
for each (let consumer in prompt.consumers) {
for (let consumer of prompt.consumers) {
if (!consumer.callback) {
// Not having a callback means that consumer didn't provide it
// or canceled the notification.
+1 -1
View File
@@ -38,7 +38,7 @@ addLoadEvent(function() {
}
}
for each(var event in ["online", "offline"]) {
for (var event of ["online", "offline"]) {
document.documentElement.addEventListener(
event,
makeHandler("document.body.addEventListener('%1', ..., false)",
+2 -2
View File
@@ -67,12 +67,12 @@ function createTestEventValue(name) {
is(ev.pointerId, pointerId, "Correct pointerId");
}
for each (let target in [document, window, testTarget, parent])
for (let target of [document, window, testTarget, parent])
target.addEventListener(name, check, false);
testTarget.dispatchEvent(event);
for each (let target in [document, window, testTarget, parent])
for (let target of [document, window, testTarget, parent])
target.removeEventListener(name, check, false);
+12 -12
View File
@@ -29,12 +29,12 @@ and
<script class="testbody" type="text/javascript; version=1.7">
/** Test for Bug 418756 and 617528 **/
let group1;
let group2;
let group3;
var group1;
var group2;
var group3;
let tags = ["input", "menuitem"];
for each (let tag in tags) {
var tags = ["input", "menuitem"];
for (let tag of tags) {
function bounce(node) {
let n = node.nextSibling;
@@ -43,10 +43,10 @@ function bounce(node) {
p.insertBefore(node, n);
}
let createdNodes = [];
var createdNodes = [];
function cleanup() {
for each (let node in createdNodes) {
for (let node of createdNodes) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
@@ -55,12 +55,12 @@ function cleanup() {
createdNodes = [];
}
let typeMapper = {
var typeMapper = {
'c': 'checkbox',
'r': 'radio'
};
let id = 0;
var id = 0;
// type can be 'c' for 'checkbox' and 'r' for 'radio'
function createNode(type, name, checked) {
@@ -75,11 +75,11 @@ function createNode(type, name, checked) {
return node;
}
let types = ['c', 'r'];
var types = ['c', 'r'];
// First make sure that setting .checked makes .defaultChecked changes no
// longer affect .checked.
for each (let type in types) {
for (let type of types) {
let n = createNode(type, '', false);
is(n.defaultChecked, false, "Bogus defaultChecked on " + typeMapper[type]);
is(n.checked, false, "Bogus checked on " + typeMapper[type]);
@@ -109,7 +109,7 @@ cleanup();
// Now check that bouncing a control that's the only one of its kind has no
// effect
for each (let type in types) {
for (let type of types) {
let n = createNode(type, 'test1', true);
$(tag == "input" ? "f1" : "m1").appendChild(n);
n.checked = false;
@@ -57,3 +57,21 @@ interface nsIPushNotificationService : nsISupports
*/
jsval clearForDomain(in string domain);
};
[scriptable, uuid(a2555e70-46f8-4b52-bf02-d978b979d143)]
interface nsIPushQuotaManager : nsISupports
{
/**
* Informs the quota manager that a notification
* for the given origin has been shown. Used to
* determine if push quota should be relaxed.
*/
void notificationForOriginShown(in string origin);
/**
* Informs the quota manager that a notification
* for the given origin has been closed. Used to
* determine if push quota should be relaxed.
*/
void notificationForOriginClosed(in string origin);
};
+1 -1
View File
@@ -25,7 +25,7 @@ function isNetworkReady() {
for (var i = 0; i < num; i++) {
var ips = {};
var prefixLengths = {};
var length = itfList.getInterface(i).getAddresses(ips, prefixLengths);
var length = itfList.getInterfaceInfo(i).getAddresses(ips, prefixLengths);
for (var j = 0; j < length; j++) {
var ip = ips.value[j];
+1 -1
View File
@@ -1647,7 +1647,7 @@ MmsService.prototype = {
savable.receivers = [];
// We don't have Bcc in recevied MMS message.
for each (let type in ["cc", "to"]) {
for (let type of ["cc", "to"]) {
if (intermediate.headers[type]) {
if (intermediate.headers[type] instanceof Array) {
for (let index in intermediate.headers[type]) {
+46 -15
View File
@@ -673,7 +673,7 @@ MobileMessageDB.prototype = {
stores = txn.objectStore(storeNames[0]);
} else {
stores = [];
for each (let storeName in storeNames) {
for (let storeName of storeNames) {
if (DEBUG) debug("Retrieving object store " + storeName);
stores.push(txn.objectStore(storeName));
}
@@ -1460,7 +1460,7 @@ MobileMessageDB.prototype = {
let timestamp = messageRecord.timestamp;
// Setup participantIdsIndex.
messageRecord.participantIdsIndex = [];
for each (let id in participantIds) {
for (let id of participantIds) {
messageRecord.participantIdsIndex.push([id, timestamp]);
}
if (threadRecord) {
@@ -2857,7 +2857,7 @@ MobileMessageDB.prototype = {
aMessageRecord.threadIdIndex = [threadId, timestamp];
// Setup participantIdsIndex.
aMessageRecord.participantIdsIndex = [];
for each (let id in participantIds) {
for (let id of participantIds) {
aMessageRecord.participantIdsIndex.push([id, timestamp]);
}
@@ -3827,8 +3827,35 @@ MobileMessageDB.prototype = {
}
if (segmentRecord.segments[seq]) {
if (DEBUG) debug("Got duplicated segment no. " + seq);
return;
if (segmentRecord.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET &&
segmentRecord.encoding == aSmsSegment.encoding &&
segmentRecord.segments[seq].length == aSmsSegment.data.length &&
segmentRecord.segments[seq].every(function(aElement, aIndex) {
return aElement == aSmsSegment.data[aIndex];
})) {
if (DEBUG) {
debug("Got duplicated binary segment no: " + seq);
}
return;
}
if (segmentRecord.encoding != RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET &&
aSmsSegment.encoding != RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET &&
segmentRecord.segments[seq] == aSmsSegment.body) {
if (DEBUG) {
debug("Got duplicated text segment no: " + seq);
}
return;
}
// Update mandatory properties to ensure that the segments could be
// concatenated properly.
segmentRecord.encoding = aSmsSegment.encoding;
segmentRecord.originatorPort = aSmsSegment.originatorPort;
segmentRecord.destinationPort = aSmsSegment.destinationPort;
segmentRecord.teleservice = aSmsSegment.teleservice;
// Decrease the counter for this collided segment.
segmentRecord.receivedSegments--;
}
segmentRecord.timestamp = aSmsSegment.timestamp;
@@ -3847,11 +3874,11 @@ MobileMessageDB.prototype = {
// save it into the segmentRecord.
if (aSmsSegment.teleservice === RIL.PDU_CDMA_MSG_TELESERIVCIE_ID_WAP
&& seq === 1) {
if (aSmsSegment.originatorPort === Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID) {
if (aSmsSegment.originatorPort !== Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID) {
segmentRecord.originatorPort = aSmsSegment.originatorPort;
}
if (aSmsSegment.destinationPort === Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID) {
if (aSmsSegment.destinationPort !== Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID) {
segmentRecord.destinationPort = aSmsSegment.destinationPort;
}
}
@@ -4099,8 +4126,11 @@ MobileMessageDB.prototype = {
* @param {boolean} value
* The updated <code>read</code> value.
* @param {boolean} aSendReadReport
* <code>true</code> to update the <code>isReadReportSent</code>
* property if the message is MMS.
* <code>true</code> to reply the read report of an incoming MMS
* message whose <code>isReadReportSent</code> is 'false'.
* Note: <code>isReadReportSent</code> will be set to 'true' no
* matter aSendReadReport is true or not when a message was marked
* from UNREAD to READ. See bug 1180470 for the new UX policy.
* @param {nsIMobileMessageCallback} aRequest
* The callback object.
*/
@@ -4153,17 +4183,18 @@ MobileMessageDB.prototype = {
messageRecord.read = value ? FILTER_READ_READ : FILTER_READ_UNREAD;
messageRecord.readIndex = [messageRecord.read, messageRecord.timestamp];
let readReportMessageId, readReportTo;
if (aSendReadReport &&
messageRecord.type == "mms" &&
if (messageRecord.type == "mms" &&
messageRecord.delivery == DELIVERY_RECEIVED &&
messageRecord.read == FILTER_READ_READ &&
messageRecord.headers["x-mms-read-report"] &&
!messageRecord.isReadReportSent) {
messageRecord.isReadReportSent = true;
let from = messageRecord.headers["from"];
readReportTo = from && from.address;
readReportMessageId = messageRecord.headers["message-id"];
if (aSendReadReport) {
let from = messageRecord.headers["from"];
readReportTo = from && from.address;
readReportMessageId = messageRecord.headers["message-id"];
}
}
if (DEBUG) debug("Message.read set to: " + value);
@@ -4240,7 +4271,7 @@ MobileMessageDB.prototype = {
}
};
let FilterSearcherHelper = {
var FilterSearcherHelper = {
/**
* @param index
+28 -5
View File
@@ -551,12 +551,35 @@ SmsService.prototype = {
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(aSegment));
if (options.encoding == Ci.nsIGonkSmsService.SMS_MESSAGE_ENCODING_8BITS_ALPHABET &&
options.encoding == aSegment.encoding &&
options.segments[seq].length == aSegment.data.length &&
options.segments[seq].every(function(aElement, aIndex) {
return aElement == aSegment.data[aIndex];
})) {
if (DEBUG) {
debug("Got duplicated binary segment no: " + seq);
}
return null;
}
return null;
if (options.encoding != Ci.nsIGonkSmsService.SMS_MESSAGE_ENCODING_8BITS_ALPHABET &&
aSegment.encoding != Ci.nsIGonkSmsService.SMS_MESSAGE_ENCODING_8BITS_ALPHABET &&
options.segments[seq] == aSegment.body) {
if (DEBUG) {
debug("Got duplicated text segment no: " + seq);
}
return null;
}
// Update mandatory properties to ensure that the segments could be
// concatenated properly.
options.encoding = aSegment.encoding;
options.originatorPort = aSegment.originatorPort;
options.destinationPort = aSegment.destinationPort;
options.teleservice = aSegment.teleservice;
// Decrease the counter for this collided segment.
options.receivedSegments--;
}
if (options.receivedSegments > 0) {
@@ -43,6 +43,7 @@ qemu = true
[test_mmdb_full_storage.js]
[test_mmdb_upgradeSchema_current_structure.js]
[test_mmdb_upgradeSchema_22.js]
[test_mmdb_ports_in_cdma_wappush.js]
[test_replace_short_message_type.js]
[test_mt_sms_concatenation.js]
[test_error_of_mms_manual_retrieval.js]
@@ -0,0 +1,74 @@
/* 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_ports_in_cdma_wappush:" + newUUID();
const TEST_PDU = [
{
sender: "+0987654321",
iccId: "1029384756",
segmentRef: 0,
segmentSeq: 1, // 1st segment
segmentMaxSeq: 2,
encoding: 0x04, // 8-bit encoding
data: [0, 1, 2],
teleservice: 0x1004, // PDU_CDMA_MSG_TELESERIVCIE_ID_WAP
// Port numbers are only provided in 1st segment from CDMA SMS PDUs.
originatorPort: 9200, // WDP_PORT_PUSH (server-side)
destinationPort: 2948 // WDP_PORT_PUSH (client-side)
},
{
sender: "+0987654321",
iccId: "1029384756",
segmentRef: 0,
segmentSeq: 2, // 2nd segment
segmentMaxSeq: 2,
encoding: 0x04, // 8-bit encoding
data: [3, 4, 5],
teleservice: 0x1004, // PDU_CDMA_MSG_TELESERIVCIE_ID_WAP
// Port numbers are only provided in 1st segment from CDMA SMS PDUs.
originatorPort: Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID,
destinationPort: Ci.nsIGonkSmsService.SMS_APPLICATION_PORT_INVALID
}
];
function testSaveCdmaWapPush(aMmdb, aReverse) {
log("testSaveCdmaWapPush(), aReverse: " + aReverse);
let testPDUs = aReverse ? Array.from(TEST_PDU).reverse() : TEST_PDU;
let lengthOfFullData = 0;
let promises = [];
for (let pdu of testPDUs) {
lengthOfFullData += pdu.data.length;
promises.push(saveSmsSegment(aMmdb, pdu));
};
return Promise.all(promises)
.then((aResults) => {
// Complete message shall be returned after 2 segments are saved.
let completeMsg = aResults[1][1];
is(completeMsg.originatorPort, TEST_PDU[0].originatorPort, "originatorPort");
is(completeMsg.destinationPort, TEST_PDU[0].destinationPort, "destinationPort");
is(completeMsg.fullData.length, lengthOfFullData, "fullData.length");
for (let i = 0; i < lengthOfFullData; i++) {
is(completeMsg.fullData[i], i, "completeMsg.fullData[" + i + "]");
}
});
}
startTestBase(function testCaseMain() {
let mmdb = newMobileMessageDB();
return initMobileMessageDB(mmdb, DBNAME, 0)
.then(() => testSaveCdmaWapPush(mmdb, false))
.then(() => testSaveCdmaWapPush(mmdb, true))
.then(() => closeMobileMessageDB(mmdb));
});
@@ -171,7 +171,7 @@ let LEGACY = {
aMessageRecord.threadIdIndex = [threadId, timestamp];
// Setup participantIdsIndex.
aMessageRecord.participantIdsIndex = [];
for each (let id in participantIds) {
for (let id of participantIds) {
aMessageRecord.participantIdsIndex.push([id, timestamp]);
}
@@ -25,8 +25,8 @@ function byteValueToHexString(aValue) {
return str.length == 1 ? "0" + str : str;
}
let ref_num = 0;
function buildTextPdus(aDcs) {
var ref_num = 0;
function buildTextPdus(aDcs, aInsertDuplication = false) {
ref_num++;
let IEI_CONCATE_1 = "0003" + byteValueToHexString(ref_num) + "0301";
@@ -42,14 +42,24 @@ function buildTextPdus(aDcs) {
let PDU_COMMON = PDU_SMSC_NONE + PDU_FIRST_OCTET + PDU_SENDER +
PDU_PID_NORMAL + aDcs + PDU_TIMESTAMP + PDU_UDL + PDU_UDHL;
return [
let pdus = [
PDU_COMMON + IEI_CONCATE_1 + PDU_UD_A,
PDU_COMMON + IEI_CONCATE_2 + PDU_UD_B,
PDU_COMMON + IEI_CONCATE_3 + PDU_UD_C
];
if (aInsertDuplication) {
log("Insert Duplicated text PDU");
// The 2nd PDU_UD_A is expected to be ignored.
pdus.unshift(PDU_COMMON + IEI_CONCATE_1 + PDU_UD_A);
// Expected to be replaced with PDU_UD_A;
pdus.unshift(PDU_COMMON + IEI_CONCATE_1 + PDU_UD_C);
}
return pdus;
}
function buildBinaryPdus(aDcs) {
function buildBinaryPdus(aDcs, aInsertDuplication = false) {
ref_num++;
let IEI_PORT = "05040B8423F0";
@@ -81,10 +91,20 @@ function buildBinaryPdus(aDcs) {
return udl + ud;
}
return [
let pdus = [
PDU_COMMON + construstBinaryUserData(PDU_DATA1, 1),
PDU_COMMON + construstBinaryUserData(PDU_DATA2, 2)
];
if (aInsertDuplication) {
log("Insert Duplicated binary PDU");
// The 2nd PDU_DATA1 is expected to be ignored.
pdus.unshift(PDU_COMMON + construstBinaryUserData(PDU_DATA1, 1));
// Expected to be replaced with PDU_DATA1;
pdus.unshift(PDU_COMMON + construstBinaryUserData(PDU_DATA2, 1));
}
return pdus;
}
function verifyTextMessage(aMessage, aMessageClass) {
@@ -104,12 +124,16 @@ function verifyBinaryMessage(aMessage) {
function testText(aDcs, aClass) {
log("testText(): aDcs = " + aDcs + ", aClass = " + aClass);
return sendMultipleRawSmsToEmulatorAndWait(buildTextPdus(aDcs))
.then((results) => verifyTextMessage(results[0].message, aClass))
.then(() => sendMultipleRawSmsToEmulatorAndWait(buildTextPdus(aDcs, true)))
.then((results) => verifyTextMessage(results[0].message, aClass));
}
function testBinary(aDcs) {
log("testBinary(): aDcs = " + aDcs);
return sendMultipleRawSmsToEmulatorAndWait(buildBinaryPdus(aDcs))
.then((results) => verifyBinaryMessage(results[0].message))
.then(() => sendMultipleRawSmsToEmulatorAndWait(buildBinaryPdus(aDcs, true)))
.then((results) => verifyBinaryMessage(results[0].message));
}
+17 -17
View File
@@ -26,8 +26,8 @@ const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471
const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
const NET_TYPE_WIFI = Ci.nsINetworkInfo.NETWORK_TYPE_WIFI;
const NET_TYPE_MOBILE = Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE;
// Networks have different status that NetworkStats API needs to be aware of.
// Network is present and ready, so NetworkManager provides the whole info.
@@ -192,10 +192,10 @@ this.NetworkStatsService = {
// the stats are updated for the new interface without waiting to
// complete the updating period.
let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
debug("Network " + network.name + " of type " + network.type + " status change");
let networkInfo = aSubject.QueryInterface(Ci.nsINetworkInfo);
debug("Network " + networkInfo.name + " of type " + networkInfo.type + " status change");
let netId = this.convertNetworkInterface(network);
let netId = this.convertNetworkInfo(networkInfo);
if (!netId) {
break;
}
@@ -269,33 +269,33 @@ this.NetworkStatsService = {
return networks;
},
convertNetworkInterface: function(aNetwork) {
if (aNetwork.type != NET_TYPE_MOBILE &&
aNetwork.type != NET_TYPE_WIFI) {
convertNetworkInfo: function(aNetworkInfo) {
if (aNetworkInfo.type != NET_TYPE_MOBILE &&
aNetworkInfo.type != NET_TYPE_WIFI) {
return null;
}
let id = '0';
if (aNetwork.type == NET_TYPE_MOBILE) {
if (!(aNetwork instanceof Ci.nsIRilNetworkInterface)) {
debug("Error! Mobile network should be an nsIRilNetworkInterface!");
if (aNetworkInfo.type == NET_TYPE_MOBILE) {
if (!(aNetworkInfo instanceof Ci.nsIRilNetworkInfo)) {
debug("Error! Mobile network should be an nsIRilNetworkInfo!");
return null;
}
let rilNetwork = aNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
let rilNetwork = aNetworkInfo.QueryInterface(Ci.nsIRilNetworkInfo);
id = rilNetwork.iccId;
}
let netId = this.getNetworkId(id, aNetwork.type);
let netId = this.getNetworkId(id, aNetworkInfo.type);
if (!this._networks[netId]) {
this._networks[netId] = Object.create(null);
this._networks[netId].network = { id: id,
type: aNetwork.type };
type: aNetworkInfo.type };
}
this._networks[netId].status = NETWORK_STATUS_READY;
this._networks[netId].interfaceName = aNetwork.name;
this._networks[netId].interfaceName = aNetworkInfo.name;
return netId;
},
@@ -738,10 +738,10 @@ this.NetworkStatsService = {
/*
* Function responsible for receiving stats which are not from netd.
*/
saveStats: function saveStats(aAppId, aIsInBrowser, aServiceType, aNetwork,
saveStats: function saveStats(aAppId, aIsInBrowser, aServiceType, aNetworkInfo,
aTimeStamp, aRxBytes, aTxBytes, aIsAccumulative,
aCallback) {
let netId = this.convertNetworkInterface(aNetwork);
let netId = this.convertNetworkInfo(aNetworkInfo);
if (!netId) {
if (aCallback) {
aCallback(false, "Invalid network type");
+10 -10
View File
@@ -29,19 +29,19 @@ NetworkStatsServiceProxy.prototype = {
* Function called in the protocol layer (HTTP, FTP, WebSocket ...etc)
* to pass the per-app stats to NetworkStatsService.
*/
saveAppStats: function saveAppStats(aAppId, aIsInBrowser, aNetwork, aTimeStamp,
saveAppStats: function saveAppStats(aAppId, aIsInBrowser, aNetworkInfo, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback) {
if (!aNetwork) {
if (!aNetworkInfo) {
if (DEBUG) {
debug("|aNetwork| is not specified. Failed to save stats. Returning.");
debug("|aNetworkInfo| is not specified. Failed to save stats. Returning.");
}
return;
}
if (DEBUG) {
debug("saveAppStats: " + aAppId + " " + aIsInBrowser + " " +
aNetwork.type + " " + aTimeStamp + " " +
aNetworkInfo.type + " " + aTimeStamp + " " +
aRxBytes + " " + aTxBytes + " " + aIsAccumulative);
}
@@ -49,7 +49,7 @@ NetworkStatsServiceProxy.prototype = {
aCallback = aCallback.notify;
}
NetworkStatsService.saveStats(aAppId, aIsInBrowser, "", aNetwork,
NetworkStatsService.saveStats(aAppId, aIsInBrowser, "", aNetworkInfo,
aTimeStamp, aRxBytes, aTxBytes,
aIsAccumulative, aCallback);
},
@@ -58,18 +58,18 @@ NetworkStatsServiceProxy.prototype = {
* Function called in the points of different system services
* to pass the per-service stats to NetworkStatsService.
*/
saveServiceStats: function saveServiceStats(aServiceType, aNetwork,
saveServiceStats: function saveServiceStats(aServiceType, aNetworkInfo,
aTimeStamp, aRxBytes, aTxBytes,
aIsAccumulative, aCallback) {
if (!aNetwork) {
if (!aNetworkInfo) {
if (DEBUG) {
debug("|aNetwork| is not specified. Failed to save stats. Returning.");
debug("|aNetworkInfo| is not specified. Failed to save stats. Returning.");
}
return;
}
if (DEBUG) {
debug("saveServiceStats: " + aServiceType + " " + aNetwork.type + " " +
debug("saveServiceStats: " + aServiceType + " " + aNetworkInfo.type + " " +
aTimeStamp + " " + aRxBytes + " " + aTxBytes + " " +
aIsAccumulative);
}
@@ -78,7 +78,7 @@ NetworkStatsServiceProxy.prototype = {
aCallback = aCallback.notify;
}
NetworkStatsService.saveStats(0, false, aServiceType ,aNetwork, aTimeStamp,
NetworkStatsService.saveStats(0, false, aServiceType , aNetworkInfo, aTimeStamp,
aRxBytes, aTxBytes, aIsAccumulative,
aCallback);
},
@@ -4,7 +4,7 @@
#include "nsISupports.idl"
interface nsINetworkInterface;
interface nsINetworkInfo;
[scriptable, function, uuid(5f821529-1d80-4ab5-a933-4e1b3585b6bc)]
interface nsINetworkStatsServiceProxyCallback : nsISupports
@@ -16,7 +16,7 @@ interface nsINetworkStatsServiceProxyCallback : nsISupports
void notify(in boolean aResult, in jsval aMessage);
};
[scriptable, uuid(98fd8f69-784e-4626-aa59-56d6436a3c24)]
[scriptable, uuid(f4f3e901-e102-499d-9d37-dc9951f52df7)]
interface nsINetworkStatsServiceProxy : nsISupports
{
/*
@@ -32,7 +32,7 @@ interface nsINetworkStatsServiceProxy : nsISupports
*/
void saveAppStats(in unsigned long aAppId,
in boolean aIsInBrowser,
in nsINetworkInterface aNetwork,
in nsINetworkInfo aNetworkInfo,
in unsigned long long aTimeStamp,
in unsigned long long aRxBytes,
in unsigned long long aTxBytes,
@@ -50,7 +50,7 @@ interface nsINetworkStatsServiceProxy : nsISupports
* @param aCallback an optional callback
*/
void saveServiceStats(in string aServiceType,
in nsINetworkInterface aNetwork,
in nsINetworkInfo aNetworkInfo,
in unsigned long long aTimeStamp,
in unsigned long long aRxBytes,
in unsigned long long aTxBytes,
@@ -936,7 +936,7 @@ add_test(function test_addAlarm() {
netStatsDb.addAlarm(alarms[0], function(error, result) {
do_check_eq(error, null);
alarmsDbId = result;
netStatsDb.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, exampleManifestURL, function(error, result) {
netStatsDb.getAlarms(Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, exampleManifestURL, function(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
do_check_eq(result[0].id, alarmsDbId);
@@ -260,7 +260,7 @@ add_test(function test_fireAlarm() {
NetworkStatsService._networks[wifiId].status = NETWORK_STATUS_STANDBY;
NetworkStatsService._db.addAlarm(alarm, function addSuccessCb(error, newId) {
NetworkStatsService._db.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
NetworkStatsService._db.getAlarms(Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
testManifestURL, function onGet(error, result) {
do_check_eq(error, null);
do_check_eq(result.length, 1);
@@ -272,7 +272,7 @@ add_test(function test_fireAlarm() {
result[0].manifestURL = testManifestURL;
NetworkStatsService._fireAlarm(result[0], false);
NetworkStatsService._db.getAlarms(Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
NetworkStatsService._db.getAlarms(Ci.nsINetworkInfo.NETWORK_TYPE_WIFI,
testManifestURL, function onGet(error, result) {
do_check_eq(error, undefined);
do_check_eq(result.length, 0);
@@ -9,24 +9,24 @@ XPCOMUtils.defineLazyServiceGetter(this, "nssProxy",
"@mozilla.org/networkstatsServiceProxy;1",
"nsINetworkStatsServiceProxy");
function mokConvertNetworkInterface() {
NetworkStatsService.convertNetworkInterface = function(aNetwork) {
if (aNetwork.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
aNetwork.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
function mokConvertNetworkInfo() {
NetworkStatsService.convertNetworkInfo = function(aNetworkInfo) {
if (aNetworkInfo.type != Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE &&
aNetworkInfo.type != Ci.nsINetworkInfo.NETWORK_TYPE_WIFI) {
return null;
}
let id = '0';
if (aNetwork.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
if (aNetworkInfo.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE) {
id = '1234'
}
let netId = this.getNetworkId(id, aNetwork.type);
let netId = this.getNetworkId(id, aNetworkInfo.type);
if (!this._networks[netId]) {
this._networks[netId] = Object.create(null);
this._networks[netId].network = { id: id,
type: aNetwork.type };
type: aNetworkInfo.type };
}
return netId;
@@ -37,13 +37,12 @@ add_test(function test_saveAppStats() {
var cachedStats = NetworkStatsService.cachedStats;
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
// be instantiated, these two vars will emulate it by filling the properties
// that will be used.
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
// Create to fake nsINetworkInfos. As nsINetworkInfo can not be instantiated,
// these two vars will emulate it by filling the properties that will be used.
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network interface in NetworkStatsService
// Insert fake mobile network info in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
do_check_eq(Object.keys(cachedStats).length, 0);
@@ -83,13 +82,12 @@ add_test(function test_saveAppStats() {
add_test(function test_saveServiceStats() {
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
// be instantiated, these two vars will emulate it by filling the properties
// that will be used.
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
// Create to fake nsINetworkInfos. As nsINetworkInfo can not be instantiated,
// these two vars will emulate it by filling the properties that will be used.
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network interface in NetworkStatsService
// Insert fake mobile network info in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
NetworkStatsService.updateCachedStats(function (success, msg) {
@@ -138,7 +136,7 @@ add_test(function test_saveStatsWithDifferentDates() {
var today = NetworkStatsService.cachedStatsDate;
var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000));
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
NetworkStatsService.updateCachedStats(function (success, message) {
do_check_eq(success, true);
@@ -173,7 +171,7 @@ add_test(function test_saveStatsWithDifferentDates() {
add_test(function test_saveStatsWithMaxCachedTraffic() {
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
var maxtraffic = NetworkStatsService.maxCachedTraffic;
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
NetworkStatsService.updateCachedStats(function (success, message) {
do_check_eq(success, true);
@@ -199,11 +197,11 @@ add_test(function test_saveAppStats() {
var cachedStats = NetworkStatsService.cachedStats;
var timestamp = NetworkStatsService.cachedStatsDate.getTime();
// Create to fake nsINetworkInterfaces. As nsINetworkInterface can not
// Create to fake nsINetworkInfo. As nsINetworkInfo can not
// be instantiated, these two vars will emulate it by filling the properties
// that will be used.
var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"};
var wifi = {type: Ci.nsINetworkInfo.NETWORK_TYPE_WIFI, id: "0"};
var mobile = {type: Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE, id: "1234"};
// Insert fake mobile network interface in NetworkStatsService
var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type);
@@ -232,9 +230,9 @@ function run_test() {
Cu.import("resource://gre/modules/NetworkStatsService.jsm");
// Function convertNetworkInterface of NetworkStatsService causes errors when dealing
// Function convertNetworkInfo of NetworkStatsService causes errors when dealing
// with RIL to get the iccid, so overwrite it.
mokConvertNetworkInterface();
mokConvertNetworkInfo();
run_next_test();
}
+34
View File
@@ -51,6 +51,10 @@
#include "nsIDOMDesktopNotification.h"
#endif
#ifndef MOZ_SIMPLEPUSH
#include "nsIPushNotificationService.h"
#endif
namespace mozilla {
namespace dom {
@@ -651,6 +655,8 @@ protected:
{
AssertIsOnMainThread();
}
nsresult AdjustPushQuota(const char* aTopic);
};
NS_IMPL_ISUPPORTS(NotificationObserver, nsIObserver)
@@ -1172,11 +1178,39 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
ContentChild::GetSingleton()->SendOpenNotificationSettings(
IPC::Principal(mPrincipal));
return NS_OK;
} else if (!strcmp("alertshow", aTopic) ||
!strcmp("alertfinished", aTopic)) {
Unused << NS_WARN_IF(NS_FAILED(AdjustPushQuota(aTopic)));
}
return mObserver->Observe(aSubject, aTopic, aData);
}
nsresult
NotificationObserver::AdjustPushQuota(const char* aTopic)
{
#ifdef MOZ_SIMPLEPUSH
return NS_ERROR_NOT_IMPLEMENTED;
#else
nsCOMPtr<nsIPushQuotaManager> pushQuotaManager =
do_GetService("@mozilla.org/push/NotificationService;1");
if (!pushQuotaManager) {
return NS_ERROR_FAILURE;
}
nsAutoCString origin;
nsresult rv = mPrincipal->GetOrigin(origin);
if (NS_FAILED(rv)) {
return rv;
}
if (!strcmp("alertshow", aTopic)) {
return pushQuotaManager->NotificationForOriginShown(origin.get());
}
return pushQuotaManager->NotificationForOriginClosed(origin.get());
#endif
}
NS_IMETHODIMP
MainThreadNotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
+2 -2
View File
@@ -45,7 +45,7 @@ let PaymentManager = {
}
} catch(e) {}
for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
for (let msgname of PAYMENT_IPC_MSG_NAMES) {
ppmm.addMessageListener(msgname, this);
}
@@ -401,7 +401,7 @@ let PaymentManager = {
observe: function observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
for (let msgname of PAYMENT_IPC_MSG_NAMES) {
ppmm.removeMessageListener(msgname, this);
}
this.registeredProviders = null;
+127 -115
View File
@@ -4,14 +4,6 @@
"use strict";
// Don't modify this, instead set dom.push.debug.
let gDebuggingEnabled = true;
function debug(s) {
if (gDebuggingEnabled)
dump("-*- Push.js: " + s + "\n");
}
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
@@ -21,6 +13,14 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
XPCOMUtils.defineLazyGetter(this, "console", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.push.loglevel",
prefix: "Push",
});
});
const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
/**
@@ -29,7 +29,7 @@ const PUSH_CID = Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
* one actually performing all operations.
*/
function Push() {
debug("Push Constructor");
console.debug("Push()");
}
Push.prototype = {
@@ -44,11 +44,7 @@ Push.prototype = {
Ci.nsIObserver]),
init: function(aWindow) {
// Set debug first so that all debugging actually works.
// NOTE: We don't add an observer here like in PushService. Flipping the
// pref will require a reload of the app/page, which seems acceptable.
gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug");
debug("init()");
console.debug("init()");
this._window = aWindow;
@@ -60,127 +56,69 @@ Push.prototype = {
},
setScope: function(scope){
debug('setScope ' + scope);
console.debug("setScope()", scope);
this._scope = scope;
},
askPermission: function (aAllowCallback, aCancelCallback) {
debug("askPermission");
console.debug("askPermission()");
let principal = this._window.document.nodePrincipal;
let type = "push";
let permValue =
Services.perms.testExactPermissionFromPrincipal(principal, type);
return this.createPromise((resolve, reject) => {
let permissionDenied = () => {
reject(new this._window.DOMException(
"User denied permission to use the Push API",
"PermissionDeniedError"
));
};
if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
aAllowCallback();
return;
}
let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
try {
permission = this._testPermission();
} catch (e) {
permissionDenied();
return;
}
if (permValue == Ci.nsIPermissionManager.DENY_ACTION) {
aCancelCallback();
return;
}
// Create an array with a single nsIContentPermissionType element.
type = {
type: "push",
access: null,
options: [],
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType])
};
let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
typeArray.appendElement(type, false);
// create a nsIContentPermissionRequest
let request = {
types: typeArray,
principal: principal,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
allow: function() {
aAllowCallback();
},
cancel: function() {
aCancelCallback();
},
window: this._window
};
// Using askPermission from nsIDOMWindowUtils that takes care of the
// remoting if needed.
let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.askPermission(request);
},
getEndpointResponse: function(fn) {
debug("GetEndpointResponse " + fn.toSource());
let that = this;
let p = this.createPromise(function(resolve, reject) {
this.askPermission(
() => {
fn(that._scope, that._principal, {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushEndpointCallback]),
onPushEndpoint: function(ok, endpoint, keyLen, key) {
if (ok === Cr.NS_OK) {
if (endpoint) {
let sub;
if (keyLen) {
let publicKey = new ArrayBuffer(keyLen);
let keyView = new Uint8Array(publicKey);
keyView.set(key);
sub = new that._window.PushSubscription(endpoint,
that._scope,
publicKey);
} else {
sub = new that._window.PushSubscription(endpoint,
that._scope,
null);
}
sub.setPrincipal(that._principal);
resolve(sub);
} else {
resolve(null);
}
} else {
reject("AbortError");
}
}
});
},
() => {
reject("PermissionDeniedError");
}
);
}.bind(this));
return p;
if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
resolve();
} else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
permissionDenied();
} else {
this._requestPermission(resolve, permissionDenied);
}
});
},
subscribe: function() {
debug("subscribe()");
console.debug("subscribe()", this._scope);
let histogram = Services.telemetry.getHistogramById("PUSH_API_USED");
histogram.add(true);
return this.getEndpointResponse(this._client.subscribe.bind(this._client));
return this.askPermission().then(() =>
this.createPromise((resolve, reject) => {
let callback = new PushEndpointCallback(this, resolve, reject);
this._client.subscribe(this._scope, this._principal, callback);
})
);
},
getSubscription: function() {
debug("getSubscription()" + this._scope);
return this.getEndpointResponse(this._client.getSubscription.bind(this._client));
console.debug("getSubscription()", this._scope);
return this.createPromise((resolve, reject) => {
let callback = new PushEndpointCallback(this, resolve, reject);
this._client.getSubscription(this._scope, this._principal, callback);
});
},
permissionState: function() {
debug("permissionState()" + this._scope);
console.debug("permissionState()", this._scope);
let p = this.createPromise((resolve, reject) => {
let permission = Ci.nsIPermissionManager.DENY_ACTION;
return this.createPromise((resolve, reject) => {
let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
try {
let permissionManager = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
permission =
permissionManager.testExactPermissionFromPrincipal(this._principal,
"push");
permission = this._testPermission();
} catch(e) {
reject();
return;
@@ -194,8 +132,82 @@ Push.prototype = {
}
resolve(pushPermissionStatus);
});
return p;
},
_testPermission: function() {
return Services.perms.testExactPermissionFromPrincipal(
this._principal, "desktop-notification");
},
_requestPermission: function(allowCallback, cancelCallback) {
// Create an array with a single nsIContentPermissionType element.
type = {
type: "push",
access: null,
options: [],
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionType]),
};
let typeArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
typeArray.appendElement(type, false);
// create a nsIContentPermissionRequest
let request = {
types: typeArray,
principal: principal,
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionRequest]),
allow: function() {
allowCallback();
},
cancel: function() {
cancelCallback();
},
window: this._window,
};
// Using askPermission from nsIDOMWindowUtils that takes care of the
// remoting if needed.
let windowUtils = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
windowUtils.askPermission(request);
},
};
function PushEndpointCallback(pushManager, resolve, reject) {
this.pushManager = pushManager;
this.resolve = resolve;
this.reject = reject;
}
PushEndpointCallback.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushEndpointCallback]),
onPushEndpoint: function(ok, endpoint, keyLen, key) {
let {pushManager} = this;
if (!Components.isSuccessCode(ok)) {
this.reject(new pushManager._window.DOMException(
"Error retrieving push subscription",
"AbortError"
));
return;
}
if (!endpoint) {
this.resolve(null);
return;
}
let publicKey = null;
if (keyLen) {
publicKey = new ArrayBuffer(keyLen);
let keyView = new Uint8Array(publicKey);
keyView.set(key);
}
let sub = new pushManager._window.PushSubscription(endpoint,
pushManager._scope,
publicKey);
sub.setPrincipal(pushManager._principal);
this.resolve(sub);
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);
+27 -29
View File
@@ -4,14 +4,6 @@
"use strict";
// Don't modify this, instead set dom.push.debug.
let gDebuggingEnabled = true;
function debug(s) {
if (gDebuggingEnabled)
dump("-*- PushClient.js: " + s + "\n");
}
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
@@ -20,6 +12,14 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "console", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.push.loglevel",
prefix: "PushClient",
});
});
const kMessages = [
"PushService:Register:OK",
"PushService:Register:KO",
@@ -30,7 +30,7 @@ const kMessages = [
];
this.PushClient = function PushClient() {
debug("PushClient created!");
console.debug("PushClient()");
this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender);
this._requests = {};
@@ -71,7 +71,7 @@ PushClient.prototype = {
},
subscribe: function(scope, principal, callback) {
debug("subscribe() " + scope);
console.debug("subscribe()", scope);
let requestId = this.addRequest(callback);
this._cpmm.sendAsyncMessage("Push:Register", {
scope: scope,
@@ -80,7 +80,7 @@ PushClient.prototype = {
},
unsubscribe: function(scope, principal, callback) {
debug("unsubscribe() " + scope);
console.debug("unsubscribe()", scope);
let requestId = this.addRequest(callback);
this._cpmm.sendAsyncMessage("Push:Unregister", {
scope: scope,
@@ -89,9 +89,10 @@ PushClient.prototype = {
},
getSubscription: function(scope, principal, callback) {
debug("getSubscription()" + scope);
console.debug("getSubscription()", scope);
let requestId = this.addRequest(callback);
debug("Going to send " + scope + " " + principal + " " + requestId);
console.debug("getSubscription: Going to send", scope, principal,
requestId);
this._cpmm.sendAsyncMessage("Push:Registration", {
scope: scope,
requestID: requestId,
@@ -99,6 +100,10 @@ PushClient.prototype = {
},
_deliverPushEndpoint: function(request, registration) {
if (!registration) {
request.onPushEndpoint(Cr.NS_OK, "", 0, null);
return;
}
if (registration.p256dhKey) {
let key = new Uint8Array(registration.p256dhKey);
request.onPushEndpoint(Cr.NS_OK,
@@ -112,38 +117,31 @@ PushClient.prototype = {
},
receiveMessage: function(aMessage) {
console.debug("receiveMessage()", aMessage);
let json = aMessage.data;
let request = this.takeRequest(json.requestID);
if (!request) {
console.error("receiveMessage: Unknown request ID", json.requestID);
return;
}
debug("receiveMessage(): " + JSON.stringify(aMessage))
switch (aMessage.name) {
case "PushService:Register:OK":
this._deliverPushEndpoint(request, json);
break;
case "PushService:Register:KO":
request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null);
break;
case "PushService:Registration:OK":
{
let endpoint = "";
if (!json.registration) {
request.onPushEndpoint(Cr.NS_OK, "", 0, null);
} else {
this._deliverPushEndpoint(request, json.registration);
}
this._deliverPushEndpoint(request, json.result);
break;
}
case "PushService:Register:KO":
case "PushService:Registration:KO":
request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null);
break;
case "PushService:Unregister:OK":
if (typeof json.result !== "boolean") {
debug("Expected boolean result from PushService!");
console.error("receiveMessage: Expected boolean for unregister response",
json.result);
request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
return;
}
@@ -154,7 +152,7 @@ PushClient.prototype = {
request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
break;
default:
debug("NOT IMPLEMENTED! receiveMessage for " + aMessage.name);
console.error("receiveMessage: NOT IMPLEMENTED!", aMessage.name);
}
},
};
+88 -62
View File
@@ -5,26 +5,24 @@
"use strict";
// Don't modify this, instead set dom.push.debug.
let gDebuggingEnabled = false;
function debug(s) {
if (gDebuggingEnabled) {
dump("-*- PushDB.jsm: " + s + "\n");
}
}
const Cu = Components.utils;
Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.importGlobalProperties(["indexedDB"]);
const prefs = new Preferences("dom.push.");
this.EXPORTED_SYMBOLS = ["PushDB"];
XPCOMUtils.defineLazyGetter(this, "console", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.push.loglevel",
prefix: "PushDB",
});
});
this.PushDB = function PushDB(dbName, dbVersion, dbStoreName, keyPath, model) {
debug("PushDB()");
console.debug("PushDB()");
this._dbStoreName = dbStoreName;
this._keyPath = keyPath;
this._model = model;
@@ -32,8 +30,6 @@ this.PushDB = function PushDB(dbName, dbVersion, dbStoreName, keyPath, model) {
// set the indexeddb database
this.initDBHelper(dbName, dbVersion,
[dbStoreName]);
gDebuggingEnabled = prefs.get("debug");
prefs.observe("debug", this);
};
this.PushDB.prototype = {
@@ -90,7 +86,7 @@ this.PushDB.prototype = {
*/
put: function(aRecord) {
debug("put()" + JSON.stringify(aRecord));
console.debug("put()", aRecord);
if (!this.isValidRecord(aRecord)) {
return Promise.reject(new TypeError(
"Scope, originAttributes, and quota are required! " +
@@ -107,8 +103,8 @@ this.PushDB.prototype = {
aTxn.result = undefined;
aStore.put(aRecord).onsuccess = aEvent => {
debug("Request successful. Updated record ID: " +
aEvent.target.result);
console.debug("put: Request successful. Updated record",
aEvent.target.result);
aTxn.result = this.toPushRecord(aRecord);
};
},
@@ -123,30 +119,18 @@ this.PushDB.prototype = {
* The ID of record to be deleted.
*/
delete: function(aKeyID) {
debug("delete()");
console.debug("delete()");
return new Promise((resolve, reject) =>
this.newTxn(
"readwrite",
this._dbStoreName,
function txnCb(aTxn, aStore) {
debug("Going to delete " + aKeyID);
aStore.delete(aKeyID);
},
resolve,
reject
)
);
},
clearAll: function clear() {
return new Promise((resolve, reject) =>
this.newTxn(
"readwrite",
this._dbStoreName,
function (aTxn, aStore) {
debug("Going to clear all!");
aStore.clear();
console.debug("delete: Removing record", aKeyID);
aStore.get(aKeyID).onsuccess = event => {
aTxn.result = event.target.result;
aStore.delete(aKeyID);
};
},
resolve,
reject
@@ -157,7 +141,7 @@ this.PushDB.prototype = {
// testFn(record) is called with a database record and should return true if
// that record should be deleted.
clearIf: function(testFn) {
debug("clearIf()");
console.debug("clearIf()");
return new Promise((resolve, reject) =>
this.newTxn(
"readwrite",
@@ -168,10 +152,12 @@ this.PushDB.prototype = {
aStore.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
if (testFn(this.toPushRecord(cursor.value))) {
let record = this.toPushRecord(cursor.value);
if (testFn(record)) {
let deleteRequest = cursor.delete();
deleteRequest.onerror = e => {
debug("Failed to delete entry even when test succeeded!");
console.error("clearIf: Error removing record",
record.keyID, e);
}
}
cursor.continue();
@@ -185,7 +171,7 @@ this.PushDB.prototype = {
},
getByPushEndpoint: function(aPushEndpoint) {
debug("getByPushEndpoint()");
console.debug("getByPushEndpoint()");
return new Promise((resolve, reject) =>
this.newTxn(
@@ -196,8 +182,9 @@ this.PushDB.prototype = {
let index = aStore.index("pushEndpoint");
index.get(aPushEndpoint).onsuccess = aEvent => {
aTxn.result = this.toPushRecord(aEvent.target.result);
debug("Fetch successful " + aEvent.target.result);
let record = this.toPushRecord(aEvent.target.result);
console.debug("getByPushEndpoint: Got record", record);
aTxn.result = record;
};
},
resolve,
@@ -207,7 +194,7 @@ this.PushDB.prototype = {
},
getByKeyID: function(aKeyID) {
debug("getByKeyID()");
console.debug("getByKeyID()");
return new Promise((resolve, reject) =>
this.newTxn(
@@ -217,8 +204,52 @@ this.PushDB.prototype = {
aTxn.result = undefined;
aStore.get(aKeyID).onsuccess = aEvent => {
aTxn.result = this.toPushRecord(aEvent.target.result);
debug("Fetch successful " + aEvent.target.result);
let record = this.toPushRecord(aEvent.target.result);
console.debug("getByKeyID: Got record", record);
aTxn.result = record;
};
},
resolve,
reject
)
);
},
/**
* Reduces all records associated with an origin to a single value.
*
* @param {String} origin The origin, matched as a prefix against the scope.
* @param {String} originAttributes Additional origin attributes. Requires
* an exact match.
* @param {Function} callback A function with the signature `(result,
* record, cursor)`, where `result` is the value returned by the previous
* invocation, `record` is the registration, and `cursor` is an `IDBCursor`.
* @param {Object} [initialValue] The value to use for the first invocation.
* @returns {Promise} Resolves with the value of the last invocation.
*/
reduceByOrigin: function(origin, originAttributes, callback, initialValue) {
console.debug("forEachOrigin()");
return new Promise((resolve, reject) =>
this.newTxn(
"readwrite",
this._dbStoreName,
(aTxn, aStore) => {
aTxn.result = initialValue;
let index = aStore.index("identifiers");
let range = IDBKeyRange.bound(
[origin, originAttributes],
[origin + "\x7f", originAttributes]
);
index.openCursor(range).onsuccess = event => {
let cursor = event.target.result;
if (!cursor) {
return;
}
let record = this.toPushRecord(cursor.value);
aTxn.result = callback(aTxn.result, record, cursor);
cursor.continue();
};
},
resolve,
@@ -229,12 +260,11 @@ this.PushDB.prototype = {
// Perform a unique match against { scope, originAttributes }
getByIdentifiers: function(aPageRecord) {
debug("getByIdentifiers() { " + aPageRecord.scope + ", " +
JSON.stringify(aPageRecord.originAttributes) + " }");
console.debug("getByIdentifiers()", aPageRecord);
if (!aPageRecord.scope || aPageRecord.originAttributes == undefined) {
return Promise.reject(
new TypeError("Scope and originAttributes are required! " +
JSON.stringify(aPageRecord)));
console.error("getByIdentifiers: Scope and originAttributes are required",
aPageRecord);
return Promise.reject(new TypeError("Invalid page record"));
}
return new Promise((resolve, reject) =>
@@ -289,7 +319,7 @@ this.PushDB.prototype = {
},
getAllKeyIDs: function() {
debug("getAllKeyIDs()");
console.debug("getAllKeyIDs()");
return new Promise((resolve, reject) =>
this.newTxn(
@@ -309,7 +339,7 @@ this.PushDB.prototype = {
},
_getAllByPushQuota: function(range) {
debug("getAllByPushQuota()");
console.debug("getAllByPushQuota()");
return new Promise((resolve, reject) =>
this.newTxn(
@@ -334,12 +364,12 @@ this.PushDB.prototype = {
},
getAllUnexpired: function() {
debug("getAllUnexpired()");
console.debug("getAllUnexpired()");
return this._getAllByPushQuota(IDBKeyRange.lowerBound(1));
},
getAllExpired: function() {
debug("getAllExpired()");
console.debug("getAllExpired()");
return this._getAllByPushQuota(IDBKeyRange.only(0));
},
@@ -365,16 +395,17 @@ this.PushDB.prototype = {
let record = aEvent.target.result;
if (!record) {
debug("update: Key ID " + aKeyID + " does not exist");
console.error("update: Record does not exist", aKeyID);
return;
}
let newRecord = aUpdateFunc(this.toPushRecord(record));
if (!this.isValidRecord(newRecord)) {
debug("update: Ignoring invalid update for key ID " + aKeyID);
console.error("update: Ignoring invalid update",
aKeyID, newRecord);
return;
}
aStore.put(newRecord).onsuccess = aEvent => {
debug("update: Update successful for key ID " + aKeyID);
console.debug("update: Update successful", aKeyID, newRecord);
aTxn.result = newRecord;
};
};
@@ -386,7 +417,7 @@ this.PushDB.prototype = {
},
drop: function() {
debug("drop()");
console.debug("drop()");
return new Promise((resolve, reject) =>
this.newTxn(
@@ -400,9 +431,4 @@ this.PushDB.prototype = {
)
);
},
observe: function observe(aSubject, aTopic, aData) {
if ((aTopic == "nsPref:changed") && (aData == "dom.push.debug"))
gDebuggingEnabled = prefs.get("debug");
}
};
+53 -45
View File
@@ -33,6 +33,39 @@ namespace dom {
using namespace workers;
namespace {
nsresult
GetPermissionState(nsIPrincipal* aPrincipal,
PushPermissionState& aState)
{
nsCOMPtr<nsIPermissionManager> permManager =
mozilla::services::GetPermissionManager();
if (!permManager) {
return NS_ERROR_FAILURE;
}
uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
nsresult rv = permManager->TestExactPermissionFromPrincipal(
aPrincipal,
"desktop-notification",
&permission);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (permission == nsIPermissionManager::ALLOW_ACTION) {
aState = PushPermissionState::Granted;
} else if (permission == nsIPermissionManager::DENY_ACTION) {
aState = PushPermissionState::Denied;
} else {
aState = PushPermissionState::Prompt;
}
return NS_OK;
}
} // anonymous namespace
class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
{
public:
@@ -393,12 +426,13 @@ public:
do_CreateInstance("@mozilla.org/push/PushClient;1");
if (!client) {
callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
return NS_OK;
}
nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
if (NS_WARN_IF(NS_FAILED(client->Unsubscribe(mScope, principal, callback)))) {
callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
return NS_ERROR_FAILURE;
return NS_OK;
}
return NS_OK;
}
@@ -488,7 +522,7 @@ public:
promise->MaybeResolve(sub);
}
} else {
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
promise->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
}
mProxy->CleanUp(aCx);
@@ -580,22 +614,20 @@ public:
RefPtr<GetSubscriptionCallback> callback = new GetSubscriptionCallback(mProxy, mScope);
nsCOMPtr<nsIPermissionManager> permManager =
mozilla::services::GetPermissionManager();
if (!permManager) {
nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
PushPermissionState state;
nsresult rv = GetPermissionState(principal, state);
if (NS_FAILED(rv)) {
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
return NS_OK;
}
nsCOMPtr<nsIPrincipal> principal = mProxy->GetWorkerPrivate()->GetPrincipal();
uint32_t permission = nsIPermissionManager::DENY_ACTION;
nsresult rv = permManager->TestExactPermissionFromPrincipal(
principal,
"push",
&permission);
if (NS_WARN_IF(NS_FAILED(rv)) || permission != nsIPermissionManager::ALLOW_ACTION) {
if (state != PushPermissionState::Granted) {
if (mAction == WorkerPushManager::GetSubscriptionAction) {
callback->OnPushEndpoint(NS_OK, EmptyString(), 0, nullptr);
return NS_OK;
}
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
return NS_OK;
}
@@ -616,7 +648,7 @@ public:
if (NS_WARN_IF(NS_FAILED(rv))) {
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
return rv;
return NS_OK;
}
return NS_OK;
@@ -646,7 +678,7 @@ WorkerPushManager::PerformSubscriptionAction(SubscriptionAction aAction, ErrorRe
RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
if (!proxy) {
p->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
return p.forget();
}
@@ -728,35 +760,11 @@ public:
return NS_OK;
}
nsCOMPtr<nsIPermissionManager> permManager =
mozilla::services::GetPermissionManager();
nsresult rv = NS_ERROR_FAILURE;
PushPermissionState state = PushPermissionState::Denied;
if (permManager) {
uint32_t permission = nsIPermissionManager::DENY_ACTION;
rv = permManager->TestExactPermissionFromPrincipal(
mProxy->GetWorkerPrivate()->GetPrincipal(),
"push",
&permission);
if (NS_SUCCEEDED(rv)) {
switch (permission) {
case nsIPermissionManager::ALLOW_ACTION:
state = PushPermissionState::Granted;
break;
case nsIPermissionManager::DENY_ACTION:
state = PushPermissionState::Denied;
break;
case nsIPermissionManager::PROMPT_ACTION:
state = PushPermissionState::Prompt;
break;
default:
MOZ_CRASH("Unexpected case!");
}
}
}
PushPermissionState state;
nsresult rv = GetPermissionState(
mProxy->GetWorkerPrivate()->GetPrincipal(),
state
);
AutoJSAPI jsapi;
jsapi.Init();
+25 -4
View File
@@ -36,10 +36,11 @@ PushNotificationService.prototype = {
_xpcom_factory: XPCOMUtils.generateSingletonFactory(PushNotificationService),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference,
Ci.nsIPushNotificationService]),
Ci.nsIPushNotificationService,
Ci.nsIPushQuotaManager,]),
register: function register(scope, originAttributes) {
return PushService._register({
return PushService.register({
scope: scope,
originAttributes: originAttributes,
maxQuota: Infinity,
@@ -47,11 +48,11 @@ PushNotificationService.prototype = {
},
unregister: function unregister(scope, originAttributes) {
return PushService._unregister({scope, originAttributes});
return PushService.unregister({scope, originAttributes});
},
registration: function registration(scope, originAttributes) {
return PushService._registration({scope, originAttributes});
return PushService.registration({scope, originAttributes});
},
clearAll: function clearAll() {
@@ -74,6 +75,26 @@ PushNotificationService.prototype = {
}
break;
}
},
// nsIPushQuotaManager methods
notificationForOriginShown: function(origin) {
if (!isParent) {
Services.cpmm.sendAsyncMessage("Push:NotificationForOriginShown", origin);
return;
}
PushService._notificationForOriginShown(origin);
},
notificationForOriginClosed: function(origin) {
if (!isParent) {
Services.cpmm.sendAsyncMessage("Push:NotificationForOriginClosed", origin);
return;
}
PushService._notificationForOriginClosed(origin);
}
};
+64 -35
View File
@@ -9,10 +9,14 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
"resource://gre/modules/Messaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
@@ -26,18 +30,6 @@ this.EXPORTED_SYMBOLS = ["PushRecord"];
const prefs = new Preferences("dom.push.");
// History transition types that can fire a `pushsubscriptionchange` event
// when the user visits a site with expired push registrations. Visits only
// count if the user sees the origin in the address bar. This excludes embedded
// resources, downloads, and framed links.
const QUOTA_REFRESH_TRANSITIONS_SQL = [
Ci.nsINavHistoryService.TRANSITION_LINK,
Ci.nsINavHistoryService.TRANSITION_TYPED,
Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY
].join(",");
function PushRecord(props) {
this.pushEndpoint = props.pushEndpoint;
this.scope = props.scope;
@@ -52,8 +44,15 @@ function PushRecord(props) {
PushRecord.prototype = {
setQuota(suggestedQuota) {
this.quota = (!isNaN(suggestedQuota) && suggestedQuota >= 0) ?
suggestedQuota : prefs.get("maxQuotaPerSubscription");
if (!isNaN(suggestedQuota) && suggestedQuota >= 0) {
this.quota = suggestedQuota;
} else {
this.resetQuota();
}
},
resetQuota() {
this.quota = prefs.get("maxQuotaPerSubscription");
},
updateQuota(lastVisit) {
@@ -68,25 +67,15 @@ PushRecord.prototype = {
this.quota = 0;
return;
}
let currentQuota;
if (lastVisit > this.lastPush) {
// If the user visited the site since the last time we received a
// notification, reset the quota.
let daysElapsed = (Date.now() - lastVisit) / 24 / 60 / 60 / 1000;
currentQuota = Math.min(
this.quota = Math.min(
Math.round(8 * Math.pow(daysElapsed, -0.8)),
prefs.get("maxQuotaPerSubscription")
);
Services.telemetry.getHistogramById("PUSH_API_QUOTA_RESET_TO").add(currentQuota - 1);
} else {
// The user hasn't visited the site since the last notification.
currentQuota = this.quota;
}
this.quota = Math.max(currentQuota - 1, 0);
// We check for ctime > 0 to skip older records that did not have ctime.
if (this.isExpired() && this.ctime > 0) {
let duration = Date.now() - this.ctime;
Services.telemetry.getHistogramById("PUSH_API_QUOTA_EXPIRATION_TIME").add(duration / 1000);
Services.telemetry.getHistogramById("PUSH_API_QUOTA_RESET_TO").add(this.quota);
}
},
@@ -96,6 +85,15 @@ PushRecord.prototype = {
this.lastPush = Date.now();
},
reduceQuota() {
this.quota = Math.max(this.quota - 1, 0);
// We check for ctime > 0 to skip older records that did not have ctime.
if (this.isExpired() && this.ctime > 0) {
let duration = Date.now() - this.ctime;
Services.telemetry.getHistogramById("PUSH_API_QUOTA_EXPIRATION_TIME").add(duration / 1000);
}
},
/**
* Queries the Places database for the last time a user visited the site
* associated with a push registration.
@@ -107,9 +105,34 @@ PushRecord.prototype = {
getLastVisit() {
if (!this.quotaApplies() || this.isTabOpen()) {
// If the registration isn't subject to quota, or the user already
// has the site open, skip the Places query.
// has the site open, skip expensive database queries.
return Promise.resolve(Date.now());
}
if (AppConstants.MOZ_ANDROID_HISTORY) {
return Messaging.sendRequestForResult({
type: "History:GetPrePathLastVisitedTimeMilliseconds",
prePath: this.uri.prePath,
}).then(result => {
if (result == 0) {
return -Infinity;
}
return result;
});
}
// Places History transition types that can fire a
// `pushsubscriptionchange` event when the user visits a site with expired push
// registrations. Visits only count if the user sees the origin in the address
// bar. This excludes embedded resources, downloads, and framed links.
const QUOTA_REFRESH_TRANSITIONS_SQL = [
Ci.nsINavHistoryService.TRANSITION_LINK,
Ci.nsINavHistoryService.TRANSITION_TYPED,
Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY
].join(",");
return PlacesUtils.withConnectionWrapper("PushRecord.getLastVisit", db => {
// We're using a custom query instead of `nsINavHistoryQueryOptions`
// because the latter doesn't expose a way to filter by transition type:
@@ -180,6 +203,14 @@ PushRecord.prototype = {
return permission == Ci.nsIPermissionManager.ALLOW_ACTION;
},
quotaChanged() {
if (!this.hasPermission()) {
return Promise.resolve(false);
}
return this.getLastVisit()
.then(lastVisit => lastVisit > this.lastPush);
},
quotaApplies() {
return Number.isFinite(this.quota);
},
@@ -188,7 +219,12 @@ PushRecord.prototype = {
return this.quota === 0;
},
toRegistration() {
matchesOriginAttributes(pattern) {
return ChromeUtils.originAttributesMatchPattern(
this.principal.originAttributes, pattern);
},
toSubscription() {
return {
pushEndpoint: this.pushEndpoint,
lastPush: this.lastPush,
@@ -196,13 +232,6 @@ PushRecord.prototype = {
p256dhKey: this.p256dhPublicKey,
};
},
toRegister() {
return {
pushEndpoint: this.pushEndpoint,
p256dhKey: this.p256dhPublicKey,
};
},
};
// Define lazy getters for the principal and scope URI. IndexedDB can't store
+506 -322
View File
File diff suppressed because it is too large Load Diff
+92 -128
View File
@@ -28,18 +28,16 @@ const {
this.EXPORTED_SYMBOLS = ["PushServiceHttp2"];
XPCOMUtils.defineLazyGetter(this, "console", () => {
let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
return new ConsoleAPI({
maxLogLevelPref: "dom.push.loglevel",
prefix: "PushServiceHttp2",
});
});
const prefs = new Preferences("dom.push.");
// Don't modify this, instead set dom.push.debug.
// Set debug first so that all debugging actually works.
var gDebuggingEnabled = prefs.get("debug");
function debug(s) {
if (gDebuggingEnabled) {
dump("-*- PushServiceHttp2.jsm: " + s + "\n");
}
}
const kPUSHHTTP2DB_DB_NAME = "pushHttp2";
const kPUSHHTTP2DB_DB_VERSION = 5; // Change this if the IndexedDB format changes
const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";
@@ -53,7 +51,7 @@ const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";
* It's easier to stop listening than to have checks at specific points.
*/
var PushSubscriptionListener = function(pushService, uri) {
debug("Creating a new pushSubscription listener.");
console.debug("PushSubscriptionListener()");
this._pushService = pushService;
this.uri = uri;
};
@@ -73,12 +71,12 @@ PushSubscriptionListener.prototype = {
},
onStartRequest: function(aRequest, aContext) {
debug("PushSubscriptionListener onStartRequest()");
console.debug("PushSubscriptionListener: onStartRequest()");
// We do not do anything here.
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
debug("PushSubscriptionListener onDataAvailable()");
console.debug("PushSubscriptionListener: onDataAvailable()");
// Nobody should send data, but just to be sure, otherwise necko will
// complain.
if (aCount === 0) {
@@ -93,7 +91,7 @@ PushSubscriptionListener.prototype = {
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
debug("PushSubscriptionListener onStopRequest()");
console.debug("PushSubscriptionListener: onStopRequest()");
if (!this._pushService) {
return;
}
@@ -104,7 +102,7 @@ PushSubscriptionListener.prototype = {
},
onPush: function(associatedChannel, pushChannel) {
debug("PushSubscriptionListener onPush()");
console.debug("PushSubscriptionListener: onPush()");
var pushChannelListener = new PushChannelListener(this);
pushChannel.asyncOpen(pushChannelListener, pushChannel);
},
@@ -119,7 +117,7 @@ PushSubscriptionListener.prototype = {
* OnDataAvailable and send to the app in OnStopRequest.
*/
var PushChannelListener = function(pushSubscriptionListener) {
debug("Creating a new push channel listener.");
console.debug("PushChannelListener()");
this._mainListener = pushSubscriptionListener;
this._message = [];
this._ackUri = null;
@@ -132,7 +130,7 @@ PushChannelListener.prototype = {
},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
debug("push channel listener onDataAvailable()");
console.debug("PushChannelListener: onDataAvailable()");
if (aCount === 0) {
return;
@@ -148,7 +146,8 @@ PushChannelListener.prototype = {
},
onStopRequest: function(aRequest, aContext, aStatusCode) {
debug("push channel listener onStopRequest() status code:" + aStatusCode);
console.debug("PushChannelListener: onStopRequest()", "status code",
aStatusCode);
if (Components.isSuccessCode(aStatusCode) &&
this._mainListener &&
this._mainListener._pushService) {
@@ -228,14 +227,14 @@ PushServiceDelete.prototype = {
if (Components.isSuccessCode(aStatusCode)) {
this._resolve();
} else {
this._reject({status: 0, error: "NetworkError"});
this._reject(new Error("Error removing subscription: " + aStatusCode));
}
}
};
var SubscriptionListener = function(aSubInfo, aResolve, aReject,
aServerURI, aPushServiceHttp2) {
debug("Creating a new subscription listener.");
console.debug("SubscriptionListener()");
this._subInfo = aSubInfo;
this._resolve = aResolve;
this._reject = aReject;
@@ -250,7 +249,7 @@ SubscriptionListener.prototype = {
onStartRequest: function(aRequest, aContext) {},
onDataAvailable: function(aRequest, aContext, aStream, aOffset, aCount) {
debug("subscription listener onDataAvailable()");
console.debug("SubscriptionListener: onDataAvailable()");
// We do not expect any data, but necko will complain if we do not consume
// it.
@@ -266,16 +265,16 @@ SubscriptionListener.prototype = {
},
onStopRequest: function(aRequest, aContext, aStatus) {
debug("subscription listener onStopRequest()");
console.debug("SubscriptionListener: onStopRequest()");
// Check if pushService is still active.
if (!this._service.hasmainPushService()) {
this._reject({error: "Service deactivated"});
this._reject(new Error("Push service unavailable"));
return;
}
if (!Components.isSuccessCode(aStatus)) {
this._reject({error: "Error status" + aStatus});
this._reject(new Error("Error listening for messages: " + aStatus));
return;
}
@@ -292,11 +291,11 @@ SubscriptionListener.prototype = {
}),
retryAfter);
} else {
this._reject({error: "Error response code: " + statusCode });
this._reject(new Error("Unexpected server response: " + statusCode));
}
return;
} else if (statusCode != 201) {
this._reject({error: "Error response code: " + statusCode });
this._reject(new Error("Unexpected server response: " + statusCode));
return;
}
@@ -304,37 +303,39 @@ SubscriptionListener.prototype = {
try {
subscriptionUri = aRequest.getResponseHeader("location");
} catch (err) {
this._reject({error: "Return code 201, but the answer is bogus"});
this._reject(new Error("Missing Location header"));
return;
}
debug("subscriptionUri: " + subscriptionUri);
console.debug("onStopRequest: subscriptionUri", subscriptionUri);
var linkList;
try {
linkList = aRequest.getResponseHeader("link");
} catch (err) {
this._reject({error: "Return code 201, but the answer is bogus"});
this._reject(new Error("Missing Link header"));
return;
}
var linkParserResult = linkParser(linkList, this._serverURI);
if (linkParserResult.error) {
this._reject(linkParserResult);
var linkParserResult;
try {
linkParserResult = linkParser(linkList, this._serverURI);
} catch (e) {
this._reject(e);
return;
}
if (!subscriptionUri) {
this._reject({error: "Return code 201, but the answer is bogus," +
" missing subscriptionUri"});
this._reject(new Error("Invalid Location header"));
return;
}
try {
let uriTry = Services.io.newURI(subscriptionUri, null, null);
} catch (e) {
debug("Invalid URI " + subscriptionUri);
this._reject({error: "Return code 201, but URI is bogus. " +
subscriptionUri});
console.error("onStopRequest: Invalid subscription URI",
subscriptionUri);
this._reject(new Error("Invalid subscription endpoint: " +
subscriptionUri));
return;
}
@@ -372,7 +373,7 @@ function linkParser(linkHeader, serverURI) {
var linkList = linkHeader.split(',');
if ((linkList.length < 1)) {
return {error: "Return code 201, but the answer is bogus"};
throw new Error("Invalid Link header");
}
var pushEndpoint;
@@ -393,32 +394,23 @@ function linkParser(linkHeader, serverURI) {
}
});
debug("pushEndpoint: " + pushEndpoint);
debug("pushReceiptEndpoint: " + pushReceiptEndpoint);
console.debug("linkParser: pushEndpoint", pushEndpoint);
console.debug("linkParser: pushReceiptEndpoint", pushReceiptEndpoint);
// Missing pushReceiptEndpoint is allowed.
if (!pushEndpoint) {
return {error: "Return code 201, but the answer is bogus, missing" +
" pushEndpoint"};
throw new Error("Missing push endpoint");
}
var uri;
var resUri = [];
try {
[pushEndpoint, pushReceiptEndpoint].forEach(u => {
if (u) {
uri = u;
resUri[u] = Services.io.newURI(uri, null, serverURI);
}
});
} catch (e) {
debug("Invalid URI " + uri);
return {error: "Return code 201, but URI is bogus. " + uri};
var pushURI = Services.io.newURI(pushEndpoint, null, serverURI);
var pushReceiptURI;
if (pushReceiptEndpoint) {
pushReceiptURI = Services.io.newURI(pushReceiptEndpoint, null,
serverURI);
}
return {
pushEndpoint: resUri[pushEndpoint].spec,
pushReceiptEndpoint: (pushReceiptEndpoint) ? resUri[pushReceiptEndpoint].spec
: ""
pushEndpoint: pushURI.spec,
pushReceiptEndpoint: (pushReceiptURI) ? pushReceiptURI.spec : "",
};
}
@@ -441,44 +433,26 @@ this.PushServiceHttp2 = {
PushRecordHttp2);
},
serviceType: function() {
return "http2";
},
hasmainPushService: function() {
return this._mainPushService !== null;
},
checkServerURI: function(serverURL) {
if (!serverURL) {
debug("No dom.push.serverURL found!");
return;
}
let uri;
try {
uri = Services.io.newURI(serverURL, null, null);
} catch(e) {
debug("Error creating valid URI from dom.push.serverURL (" +
serverURL + ")");
return null;
}
if (uri.scheme !== "https") {
debug("Unsupported websocket scheme " + uri.scheme);
return null;
}
return uri;
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "nsPref:changed") {
if (aData == "dom.push.debug") {
gDebuggingEnabled = prefs.get("debug");
}
}
validServerURI: function(serverURI) {
return serverURI.scheme == "http" || serverURI.scheme == "https";
},
connect: function(subscriptions) {
this.startConnections(subscriptions);
},
isConnected: function() {
return this._mainPushService != null;
},
disconnect: function() {
this._shutdownConnections(false);
},
@@ -508,7 +482,7 @@ this.PushServiceHttp2 = {
* Subscribe new resource.
*/
_subscribeResource: function(aRecord) {
debug("subscribeResource()");
console.debug("subscribeResource()");
return this._subscribeResourceInternal({
record: aRecord,
@@ -533,7 +507,7 @@ this.PushServiceHttp2 = {
},
_subscribeResourceInternal: function(aSubInfo) {
debug("subscribeResourceInternal()");
console.debug("subscribeResourceInternal()");
return new Promise((resolve, reject) => {
var listener = new SubscriptionListener(aSubInfo,
@@ -544,11 +518,7 @@ this.PushServiceHttp2 = {
var chan = this._makeChannel(this._serverURI.spec);
chan.requestMethod = "POST";
try {
chan.asyncOpen(listener, null);
} catch(e) {
reject({status: 0, error: "NetworkError"});
}
chan.asyncOpen(listener, null);
})
.catch(err => {
if ("retry" in err) {
@@ -564,11 +534,7 @@ this.PushServiceHttp2 = {
return new Promise((resolve,reject) => {
var chan = this._makeChannel(aUri);
chan.requestMethod = "DELETE";
try {
chan.asyncOpen(new PushServiceDelete(resolve, reject), null);
} catch(err) {
reject({status: 0, error: "NetworkError"});
}
chan.asyncOpen(new PushServiceDelete(resolve, reject), null);
});
},
@@ -577,7 +543,7 @@ this.PushServiceHttp2 = {
* We can't do anything about it if it fails, so we don't listen for response.
*/
_unsubscribeResource: function(aSubscriptionUri) {
debug("unsubscribeResource()");
console.debug("unsubscribeResource()");
return this._deleteResource(aSubscriptionUri);
},
@@ -586,9 +552,10 @@ this.PushServiceHttp2 = {
* Start listening for messages.
*/
_listenForMsgs: function(aSubscriptionUri) {
debug("listenForMsgs() " + aSubscriptionUri);
console.debug("listenForMsgs()", aSubscriptionUri);
if (!this._conns[aSubscriptionUri]) {
debug("We do not have this subscription " + aSubscriptionUri);
console.warn("listenForMsgs: We do not have this subscription",
aSubscriptionUri);
return;
}
@@ -603,7 +570,8 @@ this.PushServiceHttp2 = {
try {
chan.asyncOpen(listener, chan);
} catch (e) {
debug("Error connecting to push server. asyncOpen failed!");
console.error("listenForMsgs: Error connecting to push server.",
"asyncOpen failed", e);
conn.listener.disconnect();
chan.cancel(Cr.NS_ERROR_ABORT);
this._retryAfterBackoff(aSubscriptionUri, -1);
@@ -617,22 +585,20 @@ this.PushServiceHttp2 = {
},
_ackMsgRecv: function(aAckUri) {
debug("ackMsgRecv() " + aAckUri);
console.debug("ackMsgRecv()", aAckUri);
// We can't do anything about it if it fails,
// so we don't listen for response.
this._deleteResource(aAckUri);
},
init: function(aOptions, aMainPushService, aServerURL) {
debug("init()");
console.debug("init()");
this._mainPushService = aMainPushService;
this._serverURI = aServerURL;
gDebuggingEnabled = prefs.get("debug");
prefs.observe("debug", this);
},
_retryAfterBackoff: function(aSubscriptionUri, retryAfter) {
debug("retryAfterBackoff()");
console.debug("retryAfterBackoff()");
var resetRetryCount = prefs.get("http2.reset_retry_count_after_ms");
// If it was running for some time, reset retry counter.
@@ -672,12 +638,13 @@ this.PushServiceHttp2 = {
this._conns[aSubscriptionUri].waitingForAlarm = true;
this._mainPushService.setAlarm(retryAfter);
}
debug("Retry in " + retryAfter);
console.debug("retryAfterBackoff: Retry in", retryAfter);
},
// Close connections.
_shutdownConnections: function(deleteInfo) {
debug("shutdownConnections()");
console.debug("shutdownConnections()");
for (let subscriptionUri in this._conns) {
if (this._conns[subscriptionUri]) {
@@ -702,20 +669,21 @@ this.PushServiceHttp2 = {
// Start listening if subscriptions present.
startConnections: function(aSubscriptions) {
debug("startConnections() " + aSubscriptions.length);
console.debug("startConnections()", aSubscriptions.length);
for (let i = 0; i < aSubscriptions.length; i++) {
let record = aSubscriptions[i];
this._mainPushService.ensureP256dhKey(record).then(record => {
this._startSingleConnection(record);
}, error => {
debug("startConnections: Error updating record " + record.keyID);
console.error("startConnections: Error updating record",
record.keyID, error);
});
}
},
_startSingleConnection: function(record) {
debug("_startSingleConnection()");
console.debug("_startSingleConnection()");
if (typeof this._conns[record.subscriptionUri] != "object") {
this._conns[record.subscriptionUri] = {channel: null,
listener: null,
@@ -730,7 +698,7 @@ this.PushServiceHttp2 = {
// Start listening if subscriptions present.
_startConnectionsWaitingForAlarm: function() {
debug("startConnectionsWaitingForAlarm()");
console.debug("startConnectionsWaitingForAlarm()");
for (let subscriptionUri in this._conns) {
if ((this._conns[subscriptionUri]) &&
!this._conns[subscriptionUri].conn &&
@@ -743,7 +711,7 @@ this.PushServiceHttp2 = {
// Close connection and notify apps that subscription are gone.
_shutdownSubscription: function(aSubscriptionUri) {
debug("shutdownSubscriptions()");
console.debug("shutdownSubscriptions()");
if (typeof this._conns[aSubscriptionUri] == "object") {
if (this._conns[aSubscriptionUri].listener) {
@@ -760,7 +728,7 @@ this.PushServiceHttp2 = {
},
uninit: function() {
debug("uninit()");
console.debug("uninit()");
this._shutdownConnections(true);
this._mainPushService = null;
},
@@ -769,11 +737,12 @@ this.PushServiceHttp2 = {
request: function(action, aRecord) {
switch (action) {
case "register":
debug("register");
return this._subscribeResource(aRecord);
case "unregister":
this._shutdownSubscription(aRecord.subscriptionUri);
return this._unsubscribeResource(aRecord.subscriptionUri);
default:
return Promise.reject(new Error("Unknown request type: " + action));
}
},
@@ -801,7 +770,7 @@ this.PushServiceHttp2 = {
connOnStop: function(aRequest, aSuccess,
aSubscriptionUri) {
debug("connOnStop() succeeded: " + aSuccess);
console.debug("connOnStop() succeeded", aSuccess);
var conn = this._conns[aSubscriptionUri];
if (!conn) {
@@ -832,7 +801,7 @@ this.PushServiceHttp2 = {
},
_pushChannelOnStop: function(aUri, aAckUri, aMessage, dh, salt, rs) {
debug("pushChannelOnStop() ");
console.debug("pushChannelOnStop()");
let cryptoParams = {
dh: dh,
@@ -847,7 +816,8 @@ this.PushServiceHttp2 = {
)
.then(_ => this._ackMsgRecv(aAckUri))
.catch(err => {
debug("Error receiving message: " + err);
console.error("pushChannelOnStop: Error receiving message",
err);
});
},
@@ -870,14 +840,8 @@ PushRecordHttp2.prototype = Object.create(PushRecord.prototype, {
},
});
PushRecordHttp2.prototype.toRegistration = function() {
let registration = PushRecord.prototype.toRegistration.call(this);
registration.pushReceiptEndpoint = this.pushReceiptEndpoint;
return registration;
};
PushRecordHttp2.prototype.toRegister = function() {
let register = PushRecord.prototype.toRegister.call(this);
register.pushReceiptEndpoint = this.pushReceiptEndpoint;
return register;
PushRecordHttp2.prototype.toSubscription = function() {
let subscription = PushRecord.prototype.toSubscription.call(this);
subscription.pushReceiptEndpoint = this.pushReceiptEndpoint;
return subscription;
};
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -10,10 +10,6 @@ EXTRA_COMPONENTS += [
'PushNotificationService.js',
]
EXTRA_PP_JS_MODULES += [
'PushServiceWebSocket.jsm',
]
EXTRA_JS_MODULES += [
'PushCrypto.jsm',
'PushDB.jsm',
@@ -21,6 +17,7 @@ EXTRA_JS_MODULES += [
'PushService.jsm',
'PushServiceChildPreload.jsm',
'PushServiceHttp2.jsm',
'PushServiceWebSocket.jsm',
]
MOCHITEST_MANIFESTS += [
@@ -120,7 +120,6 @@ http://creativecommons.org/licenses/publicdomain/
SpecialPowers.pushPrefEnv({"set": [
["dom.push.enabled", true],
["dom.push.debug", true],
["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true]
@@ -324,7 +324,7 @@
["dom.serviceWorkers.testing.enabled", true],
["dom.serviceWorkers.interception.enabled", true]
]}, runTest);
SpecialPowers.addPermission('push', true, document);
SpecialPowers.addPermission('desktop-notification', true, document);
SimpleTest.waitForExplicitFinish();
</script>
</body>
+10 -3
View File
@@ -7,10 +7,12 @@ let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Task.jsm');
Cu.import('resource://gre/modules/Timer.jsm');
Cu.import('resource://gre/modules/Promise.jsm');
Cu.import('resource://gre/modules/Preferences.jsm');
Cu.import('resource://gre/modules/PlacesUtils.jsm');
Cu.import('resource://gre/modules/ObjectUtils.jsm');
const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
const servicePrefs = new Preferences('dom.push.');
@@ -180,7 +182,7 @@ function disableServiceWorkerEvents(...scopes) {
for (let scope of scopes) {
Services.perms.add(
Services.io.newURI(scope, null, null),
'push',
'desktop-notification',
Ci.nsIPermissionManager.DENY_ACTION
);
}
@@ -194,7 +196,7 @@ function disableServiceWorkerEvents(...scopes) {
*/
function setPrefs(prefs = {}) {
let defaultPrefs = Object.assign({
debug: true,
loglevel: 'all',
serverURL: 'wss://push.example.org',
'connection.enabled': true,
userAgentID: '',
@@ -221,6 +223,7 @@ function setPrefs(prefs = {}) {
'http2.retryInterval': 500,
'http2.reset_retry_count_after_ms': 60000,
maxQuotaPerSubscription: 16,
quotaUpdateDelay: 3000,
}, prefs);
for (let pref in defaultPrefs) {
servicePrefs.set(pref, defaultPrefs[pref]);
@@ -378,7 +381,11 @@ MockWebSocket.prototype = {
() => this._listener.onServerClose(this._context, statusCode, reason),
() => this._listener.onStop(this._context, Cr.NS_BASE_STREAM_CLOSED)
);
}
},
serverInterrupt(result = Cr.NS_ERROR_NET_RESET) {
waterfall(() => this._listener.onStop(this._context, result));
},
};
/**
@@ -25,7 +25,6 @@ add_task(function* test_unregister_success() {
quota: Infinity,
});
let unregisterDefer = Promise.defer();
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -0,0 +1,144 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
let clearForPattern = Task.async(function* (testRecords, pattern) {
let patternString = JSON.stringify(pattern);
yield PushService._clearOriginData(patternString);
for (let length = testRecords.length; length--;) {
let test = testRecords[length];
let originSuffix = ChromeUtils.originAttributesToSuffix(
test.originAttributes);
let registration = yield PushNotificationService.registration(
test.scope,
originSuffix
);
let url = test.scope + originSuffix;
if (ObjectUtils.deepEqual(test.clearIf, pattern)) {
ok(!registration, 'Should clear registration ' + url +
' for pattern ' + patternString);
testRecords.splice(length, 1);
} else {
ok(registration, 'Should not clear registration ' + url +
' for pattern ' + patternString);
}
}
});
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.org/1'
);
run_next_test();
}
add_task(function* test_webapps_cleardata() {
let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
let testRecords = [{
scope: 'https://example.org/1',
originAttributes: { appId: 1 },
clearIf: { appId: 1, inBrowser: false },
}, {
scope: 'https://example.org/1',
originAttributes: { appId: 1, inBrowser: true },
clearIf: { appId: 1 },
}, {
scope: 'https://example.org/1',
originAttributes: { appId: 2, inBrowser: true },
clearIf: { appId: 2, inBrowser: true },
}, {
scope: 'https://example.org/2',
originAttributes: { appId: 1 },
clearIf: { appId: 1, inBrowser: false },
}, {
scope: 'https://example.org/2',
originAttributes: { appId: 2, inBrowser: true },
clearIf: { appId: 2, inBrowser: true },
}, {
scope: 'https://example.org/3',
originAttributes: { appId: 3, inBrowser: true },
clearIf: { inBrowser: true },
}, {
scope: 'https://example.org/3',
originAttributes: { appId: 4, inBrowser: true },
clearIf: { inBrowser: true },
}];
let unregisterDone;
let unregisterPromise = new Promise(resolve =>
unregisterDone = after(testRecords.length, resolve));
PushService.init({
serverURI: "wss://push.example.org",
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(data) {
equal(data.messageType, 'hello', 'Handshake: wrong message type');
equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
},
onRegister(data) {
equal(data.messageType, 'register', 'Register: wrong message type');
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID: data.channelID,
uaid: userAgentID,
pushEndpoint: 'https://example.com/update/' + Math.random(),
}));
},
onUnregister(data) {
unregisterDone();
},
});
}
});
yield Promise.all(testRecords.map(test =>
PushNotificationService.register(
test.scope,
ChromeUtils.originAttributesToSuffix(test.originAttributes)
)
));
// Removes records for all scopes with the same app ID. Excludes records
// where `inBrowser` is true.
yield clearForPattern(testRecords, { appId: 1, inBrowser: false });
// Removes the remaining record for app ID 1, where `inBrowser` is true.
yield clearForPattern(testRecords, { appId: 1 });
// Removes all records for all scopes with the same app ID, where
// `inBrowser` is true.
yield clearForPattern(testRecords, { appId: 2, inBrowser: true });
// Removes all records where `inBrowser` is true.
yield clearForPattern(testRecords, { inBrowser: true });
equal(testRecords.length, 0, 'Should remove all test records');
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});
+153
View File
@@ -0,0 +1,153 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '2c43af06-ab6e-476a-adc4-16cbda54fb89';
var db;
var quotaURI;
var permURI;
function visitURI(uri, timestamp) {
return addVisit({
uri: uri,
title: uri.spec,
visits: [{
visitDate: timestamp * 1000,
transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
});
}
var putRecord = Task.async(function* ({scope, perm, quota, lastPush, lastVisit}) {
let uri = Services.io.newURI(scope, null, null);
Services.perms.add(uri, 'desktop-notification',
Ci.nsIPermissionManager[perm]);
do_register_cleanup(() => {
Services.perms.remove(uri, 'desktop-notification');
});
yield visitURI(uri, lastVisit);
yield db.put({
channelID: uri.path,
pushEndpoint: 'https://example.org/push' + uri.path,
scope: uri.spec,
pushCount: 0,
lastPush: lastPush,
version: null,
originAttributes: '',
quota: quota,
});
return uri;
});
function run_test() {
do_get_profile();
setPrefs({
userAgentID: userAgentID,
});
db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
run_next_test();
}
add_task(function* setUp() {
// An expired registration that should be evicted on startup. Permission is
// granted for this origin, and the last visit is more recent than the last
// push message.
yield putRecord({
scope: 'https://example.com/expired-quota-restored',
perm: 'ALLOW_ACTION',
quota: 0,
lastPush: Date.now() - 10,
lastVisit: Date.now(),
});
// An expired registration that we should evict when the origin is visited
// again.
quotaURI = yield putRecord({
scope: 'https://example.xyz/expired-quota-exceeded',
perm: 'ALLOW_ACTION',
quota: 0,
lastPush: Date.now() - 10,
lastVisit: Date.now() - 20,
});
// An expired registration that we should evict when permission is granted
// again.
permURI = yield putRecord({
scope: 'https://example.info/expired-perm-revoked',
perm: 'DENY_ACTION',
quota: 0,
lastPush: Date.now() - 10,
lastVisit: Date.now(),
});
// An active registration that we should leave alone.
yield putRecord({
scope: 'https://example.ninja/active',
perm: 'ALLOW_ACTION',
quota: 16,
lastPush: Date.now() - 10,
lastVisit: Date.now() - 20,
});
let subChangePromise = promiseObserverNotification(
'push-subscription-change',
(subject, data) => data == 'https://example.com/expired-quota-restored'
);
PushService.init({
serverURI: 'wss://push.example.org/',
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID,
}));
},
});
},
});
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
'Timed out waiting for subscription change event on startup');
});
add_task(function* test_site_visited() {
let subChangePromise = promiseObserverNotification(
'push-subscription-change',
(subject, data) => data == 'https://example.xyz/expired-quota-exceeded'
);
yield visitURI(quotaURI, Date.now());
PushService.observe(null, 'idle-daily', '');
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
'Timed out waiting for subscription change event after visit');
});
add_task(function* test_perm_restored() {
let subChangePromise = promiseObserverNotification(
'push-subscription-change',
(subject, data) => data == 'https://example.info/expired-perm-revoked'
);
Services.perms.add(permURI, 'desktop-notification',
Ci.nsIPermissionManager.ALLOW_ACTION);
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
'Timed out waiting for subscription change event after permission');
});
@@ -54,7 +54,8 @@ add_task(function* test_notification_ack() {
]);
let acks = 0;
let ackDefer = Promise.defer();
let ackDone;
let ackPromise = new Promise(resolve => ackDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -64,11 +65,6 @@ add_task(function* test_notification_ack() {
onHello(request) {
equal(request.uaid, userAgentID,
'Should send matching device IDs in handshake');
deepEqual(request.channelIDs.sort(), [
'21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
'5477bfda-22db-45d4-9614-fee369630260',
'9a5ff87f-47c9-4215-b2b8-0bdd38b4b305'
], 'Should send matching channel IDs in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
uaid: userAgentID,
@@ -115,7 +111,7 @@ add_task(function* test_notification_ack() {
channelID: '5477bfda-22db-45d4-9614-fee369630260',
version: 6
}], updates, 'Wrong updates for acknowledgement 3');
ackDefer.resolve();
ackDone();
break;
default:
@@ -128,6 +124,6 @@ add_task(function* test_notification_ack() {
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for multiple acknowledgements');
});
@@ -0,0 +1,193 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
let db;
let userAgentID = 'f5b47f8d-771f-4ea3-b999-91c135f8766d';
function run_test() {
do_get_profile();
setPrefs({
userAgentID: userAgentID,
});
run_next_test();
}
function putRecord(channelID, scope, publicKey, privateKey) {
return db.put({
channelID: channelID,
pushEndpoint: 'https://example.org/push/' + channelID,
scope: scope,
pushCount: 0,
lastPush: 0,
originAttributes: '',
quota: Infinity,
p256dhPublicKey: publicKey,
p256dhPrivateKey: privateKey,
});
}
add_task(function* test_notification_ack_data() {
db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
yield putRecord(
'subscription1',
'https://example.com/page/1',
'BPCd4gNQkjwRah61LpdALdzZKLLnU5UAwDztQ5_h0QsT26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA',
{
crv: 'P-256',
d: '1jUPhzVsRkzV0vIzwL4ZEsOlKdNOWm7TmaTfzitJkgM',
ext: true,
key_ops: ["deriveBits"],
kty: "EC",
x: '8J3iA1CSPBFqHrUul0At3NkosudTlQDAPO1Dn-HRCxM',
y: '26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA'
}
);
yield putRecord(
'subscription2',
'https://example.com/page/2',
'BPnWyUo7yMnuMlyKtERuLfWE8a09dtdjHSW2lpC9_BqR5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E',
{
crv: 'P-256',
d: 'lFm4nPsUKYgNGBJb5nXXKxl8bspCSp0bAhCYxbveqT4',
ext: true,
key_ops: ["deriveBits"],
kty: 'EC',
x: '-dbJSjvIye4yXIq0RG4t9YTxrT1212MdJbaWkL38GpE',
y: '5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E'
}
);
yield putRecord(
'subscription3',
'https://example.com/page/3',
'BDhUHITSeVrWYybFnb7ylVTCDDLPdQWMpf8gXhcWwvaaJa6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI',
{
crv: 'P-256',
d: 'Q1_SE1NySTYzjbqgWwPgrYh7XRg3adqZLkQPsy319G8',
ext: true,
key_ops: ["deriveBits"],
kty: 'EC',
x: 'OFQchNJ5WtZjJsWdvvKVVMIMMs91BYyl_yBeFxbC9po',
y: 'Ja6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI'
}
);
let updates = [];
let notifyPromise = promiseObserverNotification('push-notification', function(subject, data) {
let notification = subject.QueryInterface(Ci.nsIPushObserverNotification);
updates.push({
scope: data,
data: notification.data,
});
return updates.length == 3;
});
let acks = 0;
let ackDone;
let ackPromise = new Promise(resolve => ackDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
equal(request.uaid, userAgentID,
'Should send matching device IDs in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
uaid: userAgentID,
status: 200,
use_webpush: true,
}));
// subscription1 will send a message with no rs and padding
// length 1.
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
channelID: 'subscription1',
headers: {
encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
},
data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo',
version: 'v1',
}));
},
onACK(request) {
switch (++acks) {
case 1:
deepEqual([{
channelID: 'subscription1',
version: 'v1',
}], request.updates, 'Wrong updates for acknowledgement 1');
// subscription2 will send a message with no rs and padding
// length 16.
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
channelID: 'subscription2',
headers: {
encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
},
data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU',
version: 'v2',
}));
break;
case 2:
deepEqual([{
channelID: 'subscription2',
version: 'v2',
}], request.updates, 'Wrong updates for acknowledgement 2');
// subscription3 will send a message with rs equal 24 and
// padding length 16.
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
channelID: 'subscription3',
headers: {
encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
},
data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA',
version: 'v3',
}));
break;
case 3:
deepEqual([{
channelID: 'subscription3',
version: 'v3',
}], request.updates, 'Wrong updates for acknowledgement 3');
ackDone();
break;
default:
ok(false, 'Unexpected acknowledgement ' + acks);
}
}
});
}
});
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for multiple acknowledgements');
updates.sort((a, b) => a.scope < b.scope ? -1 : a.scope > b.scope ? 1 : 0);
deepEqual([{
scope: 'https://example.com/page/1',
data: 'Some message',
}, {
scope: 'https://example.com/page/2',
data: 'Some message',
}, {
scope: 'https://example.com/page/3',
data: 'Some message',
}], updates, 'Wrong data for notifications');
});
@@ -5,9 +5,13 @@
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '1500e7d9-8cbe-4ee6-98da-7fa5d6a39852';
function run_test() {
do_get_profile();
setPrefs();
setPrefs({
userAgentID: userAgentID,
});
disableServiceWorkerEvents(
'https://example.com/1',
'https://example.com/2'
@@ -41,8 +45,8 @@ add_task(function* test_notification_duplicate() {
let notifyPromise = promiseObserverNotification('push-notification');
let acks = 0;
let ackDefer = Promise.defer();
let ackDone = after(2, ackDefer.resolve);
let ackDone;
let ackPromise = new Promise(resolve => ackDone = after(2, resolve));
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -53,7 +57,7 @@ add_task(function* test_notification_duplicate() {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '1500e7d9-8cbe-4ee6-98da-7fa5d6a39852'
uaid: userAgentID,
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
@@ -73,7 +77,7 @@ add_task(function* test_notification_duplicate() {
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for stale acknowledgement');
let staleRecord = yield db.getByKeyID(
@@ -5,9 +5,13 @@
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '3c7462fc-270f-45be-a459-b9d631b0d093';
function run_test() {
do_get_profile();
setPrefs();
setPrefs({
userAgentID: userAgentID,
});
disableServiceWorkerEvents(
'https://example.com/a',
'https://example.com/b',
@@ -58,8 +62,8 @@ add_task(function* test_notification_error() {
)
]);
let ackDefer = Promise.defer();
let ackDone = after(records.length, ackDefer.resolve);
let ackDone;
let ackPromise = new Promise(resolve => ackDone = after(records.length, resolve));
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -74,15 +78,10 @@ add_task(function* test_notification_error() {
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
deepEqual(request.channelIDs.sort(), [
'3c3930ba-44de-40dc-a7ca-8a133ec1a866',
'b63f7bef-0a0d-4236-b41e-086a69dfd316',
'f04f1e46-9139-4826-b2d1-9411b0821283'
], 'Wrong channel list');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '3c7462fc-270f-45be-a459-b9d631b0d093'
uaid: userAgentID,
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
@@ -112,7 +111,7 @@ add_task(function* test_notification_error() {
'Wrong endpoint for notification C');
equal(cPush.version, 4, 'Wrong version for notification C');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for acknowledgements');
let aRecord = yield db.getByIdentifiers({scope: 'https://example.com/a',
@@ -5,9 +5,13 @@
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90';
function run_test() {
do_get_profile();
setPrefs();
setPrefs({
userAgentID: userAgentID,
});
disableServiceWorkerEvents(
'https://example.com/page/1',
'https://example.com/page/2',
@@ -57,8 +61,8 @@ add_task(function* test_notification_incomplete() {
ok(false, 'Should not deliver malformed updates');
}, 'push-notification', false);
let notificationDefer = Promise.defer();
let notificationDone = after(2, notificationDefer.resolve);
let notificationDone;
let notificationPromise = new Promise(resolve => notificationDone = after(2, resolve));
let prevHandler = PushServiceWebSocket._handleNotificationReply;
PushServiceWebSocket._handleNotificationReply = function _handleNotificationReply() {
notificationDone();
@@ -74,7 +78,7 @@ add_task(function* test_notification_incomplete() {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90'
uaid: userAgentID,
}));
this.serverSendMsg(JSON.stringify({
// Missing "updates" field; should ignore message.
@@ -107,7 +111,7 @@ add_task(function* test_notification_incomplete() {
}
});
yield waitForPromise(notificationDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(notificationPromise, DEFAULT_TIMEOUT,
'Timed out waiting for incomplete notifications');
let storeRecords = yield db.getAllKeyIDs();
@@ -5,9 +5,13 @@
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = 'ba31ac13-88d4-4984-8e6b-8731315a7cf8';
function run_test() {
do_get_profile();
setPrefs();
setPrefs({
userAgentID: userAgentID,
});
disableServiceWorkerEvents(
'https://example.net/case'
);
@@ -28,7 +32,8 @@ add_task(function* test_notification_version_string() {
let notifyPromise = promiseObserverNotification('push-notification');
let ackDefer = Promise.defer();
let ackDone;
let ackPromise = new Promise(resolve => ackDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -39,7 +44,7 @@ add_task(function* test_notification_version_string() {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: 'ba31ac13-88d4-4984-8e6b-8731315a7cf8'
uaid: userAgentID,
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
@@ -49,7 +54,7 @@ add_task(function* test_notification_version_string() {
}]
}));
},
onACK: ackDefer.resolve
onACK: ackDone
});
}
});
@@ -65,7 +70,7 @@ add_task(function* test_notification_version_string() {
'Wrong push endpoint');
strictEqual(message.version, 4, 'Wrong version');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for string acknowledgement');
let storeRecord = yield db.getByKeyID(
+268
View File
@@ -0,0 +1,268 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '2c43af06-ab6e-476a-adc4-16cbda54fb89';
let db;
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
});
db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
run_next_test();
}
let unregisterDefers = {};
function putRecord(channelID, scope, quota) {
return db.put({
channelID: channelID,
pushEndpoint: 'https://example.org/push/' + channelID,
scope: scope,
pushCount: 0,
lastPush: 0,
version: null,
originAttributes: '',
quota: quota,
});
}
function makePushPermission(url, capability) {
return {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPermission]),
capability: Ci.nsIPermissionManager[capability],
expireTime: 0,
expireType: Ci.nsIPermissionManager.EXPIRE_NEVER,
principal: Services.scriptSecurityManager.getCodebasePrincipal(
Services.io.newURI(url, null, null)
),
type: 'desktop-notification',
};
}
function promiseSubscriptionChanges(count) {
let notifiedScopes = [];
let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => {
notifiedScopes.push(data);
return notifiedScopes.length == count;
});
return subChangePromise.then(_ => notifiedScopes.sort());
}
function allExpired(...keyIDs) {
return Promise.all(keyIDs.map(
keyID => db.getByKeyID(keyID)
)).then(records =>
records.every(record => record.isExpired())
);
}
add_task(function* setUp() {
// Active registration; quota should be reset to 16. Since the quota isn't
// exposed to content, we shouldn't receive a subscription change event.
yield putRecord('active-allow', 'https://example.info/page/1', 8);
// Expired registration; should be dropped.
yield putRecord('expired-allow', 'https://example.info/page/2', 0);
// Active registration; should be expired when we change the permission
// to "deny".
yield putRecord('active-deny-changed', 'https://example.xyz/page/1', 16);
// Two active registrations for a visited site. These will expire when we
// add a "deny" permission.
yield putRecord('active-deny-added-1', 'https://example.net/ham', 16);
yield putRecord('active-deny-added-2', 'https://example.net/green', 8);
// An already-expired registration for a visited site. We shouldn't send an
// `unregister` request for this one, but still receive an observer
// notification when we restore permissions.
yield putRecord('expired-deny-added', 'https://example.net/eggs', 0);
// A registration that should not be affected by permission list changes
// because its quota is set to `Infinity`.
yield putRecord('never-expires', 'app://chrome/only', Infinity);
// A registration that should be dropped when we clear the permission
// list.
yield putRecord('drop-on-clear', 'https://example.edu/lonely', 16);
let handshakeDone;
let handshakePromise = new Promise(resolve => handshakeDone = resolve);
PushService.init({
serverURI: 'wss://push.example.org/',
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID,
}));
handshakeDone();
},
onUnregister(request) {
let resolve = unregisterDefers[request.channelID];
equal(typeof resolve, 'function',
'Dropped unexpected channel ID ' + request.channelID);
delete unregisterDefers[request.channelID];
resolve();
},
onACK(request) {},
});
}
});
yield waitForPromise(handshakePromise, DEFAULT_TIMEOUT,
'Timed out waiting for handshake');
});
add_task(function* test_permissions_allow_added() {
let subChangePromise = promiseSubscriptionChanges(1);
yield PushService._onPermissionChange(
makePushPermission('https://example.info', 'ALLOW_ACTION'),
'added'
);
let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications after adding allow');
deepEqual(notifiedScopes, [
'https://example.info/page/2',
], 'Wrong scopes after adding allow');
let record = yield db.getByKeyID('active-allow');
equal(record.quota, 16,
'Should reset quota for active records after adding allow');
record = yield db.getByKeyID('expired-allow');
ok(!record, 'Should drop expired records after adding allow');
});
add_task(function* test_permissions_allow_deleted() {
let unregisterPromise = new Promise(resolve => unregisterDefers[
'active-allow'] = resolve);
yield PushService._onPermissionChange(
makePushPermission('https://example.info', 'ALLOW_ACTION'),
'deleted'
);
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister after deleting allow');
let record = yield db.getByKeyID('active-allow');
ok(record.isExpired(),
'Should expire active record after deleting allow');
});
add_task(function* test_permissions_deny_added() {
let unregisterPromise = Promise.all([
new Promise(resolve => unregisterDefers[
'active-deny-added-1'] = resolve),
new Promise(resolve => unregisterDefers[
'active-deny-added-2'] = resolve),
]);
yield PushService._onPermissionChange(
makePushPermission('https://example.net', 'DENY_ACTION'),
'added'
);
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications after adding deny');
let isExpired = yield allExpired(
'active-deny-added-1',
'expired-deny-added'
);
ok(isExpired, 'Should expire all registrations after adding deny');
});
add_task(function* test_permissions_deny_deleted() {
yield PushService._onPermissionChange(
makePushPermission('https://example.net', 'DENY_ACTION'),
'deleted'
);
let isExpired = yield allExpired(
'active-deny-added-1',
'expired-deny-added'
);
ok(isExpired, 'Should retain expired registrations after deleting deny');
});
add_task(function* test_permissions_allow_changed() {
let subChangePromise = promiseSubscriptionChanges(3);
yield PushService._onPermissionChange(
makePushPermission('https://example.net', 'ALLOW_ACTION'),
'changed'
);
let notifiedScopes = yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications after changing to allow');
deepEqual(notifiedScopes, [
'https://example.net/eggs',
'https://example.net/green',
'https://example.net/ham'
], 'Wrong scopes after changing to allow');
let droppedRecords = yield Promise.all([
db.getByKeyID('active-deny-added-1'),
db.getByKeyID('active-deny-added-2'),
db.getByKeyID('expired-deny-added'),
]);
ok(!droppedRecords.some(Boolean),
'Should drop all expired registrations after changing to allow');
});
add_task(function* test_permissions_deny_changed() {
let unregisterPromise = new Promise(resolve => unregisterDefers[
'active-deny-changed'] = resolve);
yield PushService._onPermissionChange(
makePushPermission('https://example.xyz', 'DENY_ACTION'),
'changed'
);
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister after changing to deny');
let record = yield db.getByKeyID('active-deny-changed');
ok(record.isExpired(),
'Should expire active record after changing to allow');
});
add_task(function* test_permissions_clear() {
let records = yield db.getAllKeyIDs();
deepEqual(records.map(record => record.keyID).sort(), [
'active-allow',
'active-deny-changed',
'drop-on-clear',
'never-expires',
], 'Wrong records in database before clearing');
let unregisterPromise = new Promise(resolve => unregisterDefers[
'drop-on-clear'] = resolve);
yield PushService._onPermissionChange(null, 'cleared');
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister requests after clearing permissions');
records = yield db.getAllKeyIDs();
deepEqual(records.map(record => record.keyID).sort(), [
'never-expires',
], 'Unrestricted registrations should not be dropped');
});
@@ -85,7 +85,9 @@ add_task(function* test_expiration_origin_threshold() {
updates++;
return updates == 6;
});
let unregisterDefer = Promise.defer();
let unregisterDone;
let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
PushService.init({
serverURI: 'wss://push.example.org/',
@@ -94,10 +96,6 @@ add_task(function* test_expiration_origin_threshold() {
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
deepEqual(request.channelIDs.sort(), [
'46cc6f6a-c106-4ffa-bb7c-55c60bd50c41',
'eb33fc90-c883-4267-b5cb-613969e8e349',
], 'Wrong active registrations in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
@@ -127,7 +125,7 @@ add_task(function* test_expiration_origin_threshold() {
},
onUnregister(request) {
equal(request.channelID, 'eb33fc90-c883-4267-b5cb-613969e8e349', 'Unregistered wrong channel ID');
unregisterDefer.resolve();
unregisterDone();
},
// We expect to receive acks, but don't care about their
// contents.
@@ -136,7 +134,7 @@ add_task(function* test_expiration_origin_threshold() {
},
});
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister request');
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
+42 -14
View File
@@ -7,6 +7,8 @@ const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '28cd09e2-7506-42d8-9e50-b02785adc7ef';
var db;
function run_test() {
do_get_profile();
setPrefs({
@@ -15,12 +17,24 @@ function run_test() {
run_next_test();
}
let putRecord = Task.async(function* (perm, record) {
let uri = Services.io.newURI(record.scope, null, null);
Services.perms.add(uri, 'desktop-notification',
Ci.nsIPermissionManager[perm]);
do_register_cleanup(() => {
Services.perms.remove(uri, 'desktop-notification');
});
yield db.put(record);
});
add_task(function* test_expiration_history_observer() {
let db = PushServiceWebSocket.newPushDB();
db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => db.drop().then(_ => db.close()));
// A registration that we'll expire...
yield db.put({
yield putRecord('ALLOW_ACTION', {
channelID: '379c0668-8323-44d2-a315-4ee83f1a9ee9',
pushEndpoint: 'https://example.org/push/1',
scope: 'https://example.com/deals',
@@ -31,11 +45,11 @@ add_task(function* test_expiration_history_observer() {
quota: 16,
});
// ...And an expired registration that we'll revive later.
yield db.put({
channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349',
pushEndpoint: 'https://example.org/push/2',
scope: 'https://example.com/auctions',
// ...And a registration that we'll evict on startup.
yield putRecord('ALLOW_ACTION', {
channelID: '4cb6e454-37cf-41c4-a013-4e3a7fdd0bf1',
pushEndpoint: 'https://example.org/push/3',
scope: 'https://example.com/stuff',
pushCount: 0,
lastPush: 0,
version: null,
@@ -52,7 +66,10 @@ add_task(function* test_expiration_history_observer() {
}],
});
let unregisterDefer = Promise.defer();
let unregisterDone;
let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) =>
data == 'https://example.com/stuff');
PushService.init({
serverURI: 'wss://push.example.org/',
@@ -61,9 +78,6 @@ add_task(function* test_expiration_history_observer() {
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
deepEqual(request.channelIDs, [
'379c0668-8323-44d2-a315-4ee83f1a9ee9',
], 'Should not include expired channel IDs');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
@@ -79,25 +93,39 @@ add_task(function* test_expiration_history_observer() {
},
onUnregister(request) {
equal(request.channelID, '379c0668-8323-44d2-a315-4ee83f1a9ee9', 'Dropped wrong channel ID');
unregisterDefer.resolve();
unregisterDone();
},
onACK(request) {},
});
}
});
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
'Timed out waiting for subscription change event on startup');
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister request');
let expiredRecord = yield db.getByKeyID('379c0668-8323-44d2-a315-4ee83f1a9ee9');
strictEqual(expiredRecord.quota, 0, 'Expired record not updated');
let notifiedScopes = [];
let subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => {
subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => {
notifiedScopes.push(data);
return notifiedScopes.length == 2;
});
// Add an expired registration that we'll revive later.
yield putRecord('ALLOW_ACTION', {
channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349',
pushEndpoint: 'https://example.org/push/2',
scope: 'https://example.com/auctions',
pushCount: 0,
lastPush: 0,
version: null,
originAttributes: '',
quota: 0,
});
// Now visit the site...
yield addVisit({
uri: 'https://example.com/another-page',
@@ -0,0 +1,72 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
function run_test() {
do_get_profile();
setPrefs({
requestTimeout: 10000,
retryBaseInterval: 150
});
run_next_test();
}
add_task(function* test_reconnect_retry() {
let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
let registers = 0;
let channelID;
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '083e6c17-1063-4677-8638-ab705aebebc2'
}));
},
onRegister(request) {
registers++;
if (registers == 1) {
channelID = request.channelID;
this.serverClose();
return;
}
if (registers == 2) {
equal(request.channelID, channelID,
'Should retry registers after reconnect');
}
this.serverSendMsg(JSON.stringify({
messageType: 'register',
channelID: request.channelID,
pushEndpoint: 'https://example.org/push/' + request.channelID,
status: 200,
}));
}
});
}
});
let registration = yield PushNotificationService.register(
'https://example.com/page/1',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
);
let retryEndpoint = 'https://example.org/push/' + channelID;
equal(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for retried request');
registration = yield PushNotificationService.register(
'https://example.com/page/2',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
);
notEqual(registration.pushEndpoint, retryEndpoint, 'Wrong endpoint for new request')
equal(registers, 3, 'Wrong registration count');
});
@@ -79,7 +79,6 @@ add_task(function* test1() {
PushService.init({
serverURI: serverURL + "/subscribe5xxCode",
service: PushServiceHttp2,
db
});
@@ -91,15 +90,11 @@ add_task(function* test1() {
var subscriptionUri = serverURL + '/subscription';
var pushEndpoint = serverURL + '/pushEndpoint';
var pushReceiptEndpoint = serverURL + '/receiptPushEndpoint';
equal(newRecord.subscriptionUri, subscriptionUri,
'Wrong subscription ID in registration record');
equal(newRecord.pushEndpoint, pushEndpoint,
'Wrong push endpoint in registration record');
equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
'Wrong push endpoint receipt in registration record');
equal(newRecord.scope, 'https://example.com/retry5xxCode',
'Wrong scope in registration record');
let record = yield db.getByKeyID(subscriptionUri);
equal(record.subscriptionUri, subscriptionUri,
+1 -5
View File
@@ -54,12 +54,8 @@ add_task(function* test_register_case() {
);
equal(newRecord.pushEndpoint, 'https://example.com/update/case',
'Wrong push endpoint in registration record');
equal(newRecord.scope, 'https://example.net/case',
'Wrong scope in registration record');
let record = yield db.getByKeyID(newRecord.channelID);
equal(record.pushEndpoint, 'https://example.com/update/case',
'Wrong push endpoint in database record');
let record = yield db.getByPushEndpoint('https://example.com/update/case');
equal(record.scope, 'https://example.net/case',
'Wrong scope in database record');
});
@@ -49,10 +49,7 @@ add_task(function* test_pushSubscriptionNoConnection() {
PushNotificationService.register(
'https://example.net/page/invalid-response',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes("Error");
},
'Wrong error for not being able to establish connecion.'
'Expected error for not being able to establish connecion.'
);
let record = yield db.getAllKeyIDs();
@@ -90,10 +87,7 @@ add_task(function* test_pushSubscriptionMissingLocation() {
PushNotificationService.register(
'https://example.net/page/invalid-response',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes("Return code 201, but the answer is bogus");
},
'Wrong error for the missing location header.'
'Expected error for the missing location header.'
);
let record = yield db.getAllKeyIDs();
@@ -117,10 +111,7 @@ add_task(function* test_pushSubscriptionMissingLink() {
PushNotificationService.register(
'https://example.net/page/invalid-response',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes("Return code 201, but the answer is bogus");
},
'Wrong error for the missing link header.'
'Expected error for the missing link header.'
);
let record = yield db.getAllKeyIDs();
@@ -144,10 +135,7 @@ add_task(function* test_pushSubscriptionMissingLink1() {
PushNotificationService.register(
'https://example.net/page/invalid-response',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes("Return code 201, but the answer is bogus");
},
'Wrong error for the missing push endpoint.'
'Expected error for the missing push endpoint.'
);
let record = yield db.getAllKeyIDs();
@@ -171,10 +159,7 @@ add_task(function* test_pushSubscriptionLocationBogus() {
PushNotificationService.register(
'https://example.net/page/invalid-response',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes("Return code 201, but URI is bogus.");
},
'Wrong error for the bogus location'
'Expected error for the bogus location'
);
let record = yield db.getAllKeyIDs();
@@ -198,10 +183,7 @@ add_task(function* test_pushSubscriptionNot2xxCode() {
PushNotificationService.register(
'https://example.net/page/invalid-response',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes("Error");
},
'Wrong error for not 201 responce code.'
'Expected error for not 201 responce code.'
);
let record = yield db.getAllKeyIDs();
@@ -37,8 +37,8 @@ add_task(function* test_register_flush() {
let notifyPromise = promiseObserverNotification('push-notification');
let ackDefer = Promise.defer();
let ackDone = after(2, ackDefer.resolve);
let ackDone;
let ackPromise = new Promise(resolve => ackDone = after(2, resolve));
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -80,14 +80,12 @@ add_task(function* test_register_flush() {
'https://example.com/page/2', '');
equal(newRecord.pushEndpoint, 'https://example.org/update/2',
'Wrong push endpoint in record');
equal(newRecord.scope, 'https://example.com/page/2',
'Wrong scope in record');
let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notification');
equal(scope, 'https://example.com/page/1', 'Wrong notification scope');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(ackPromise, DEFAULT_TIMEOUT,
'Timed out waiting for acknowledgements');
let prevRecord = yield db.getByKeyID(
@@ -97,8 +95,6 @@ add_task(function* test_register_flush() {
strictEqual(prevRecord.version, 3,
'Should record version updates sent before register responses');
let registeredRecord = yield db.getByKeyID(newRecord.channelID);
equal(registeredRecord.pushEndpoint, 'https://example.org/update/2',
'Wrong new push endpoint');
let registeredRecord = yield db.getByPushEndpoint('https://example.org/update/2');
ok(!registeredRecord.version, 'Should not record premature updates');
});
@@ -50,10 +50,7 @@ add_task(function* test_register_invalid_channel() {
yield rejects(
PushNotificationService.register('https://example.com/invalid-channel',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'Invalid channel ID';
},
'Wrong error for invalid channel ID'
'Expected error for invalid channel ID'
);
let record = yield db.getByKeyID(channelID);
@@ -52,10 +52,7 @@ add_task(function* test_register_invalid_endpoint() {
PushNotificationService.register(
'https://example.net/page/invalid-endpoint',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error && error.includes('Invalid pushEndpoint');
},
'Wrong error for invalid endpoint'
'Expected error for invalid endpoint'
);
let record = yield db.getByKeyID(channelID);
@@ -21,8 +21,8 @@ function run_test() {
}
add_task(function* test_register_invalid_json() {
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
let helloDone;
let helloPromise = new Promise(resolve => helloDone = after(2, resolve));
let registers = 0;
PushServiceWebSocket._generateID = () => channelID;
@@ -51,13 +51,10 @@ add_task(function* test_register_invalid_json() {
yield rejects(
PushNotificationService.register('https://example.net/page/invalid-json',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for invalid JSON response'
'Expected error for invalid JSON response'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
'Reconnect after invalid JSON response timed out');
equal(registers, 1, 'Wrong register count');
});
@@ -23,8 +23,8 @@ function run_test() {
add_task(function* test_register_no_id() {
let registers = 0;
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
let helloDone;
let helloPromise = new Promise(resolve => helloDone = after(2, resolve));
PushServiceWebSocket._generateID = () => channelID;
PushService.init({
@@ -55,13 +55,10 @@ add_task(function* test_register_no_id() {
yield rejects(
PushNotificationService.register('https://example.com/incomplete',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for incomplete register response'
'Expected error for incomplete register response'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
'Reconnect after incomplete register response timed out');
equal(registers, 1, 'Wrong register count');
});
@@ -21,15 +21,16 @@ add_task(function* test_register_request_queue() {
let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
let helloDefer = Promise.defer();
let onHello = after(2, function onHello(request) {
let onHello;
let helloPromise = new Promise(resolve => onHello = after(2, function onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '54b08a9e-59c6-4ed7-bb54-f4fd60d6f606'
}));
helloDefer.resolve();
});
resolve();
}));
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -54,14 +55,10 @@ add_task(function* test_register_request_queue() {
);
yield waitForPromise(Promise.all([
rejects(firstRegister, function(error) {
return error == 'TimeoutError';
}, 'Should time out the first request'),
rejects(secondRegister, function(error) {
return error == 'TimeoutError';
}, 'Should time out the second request')
rejects(firstRegister, 'Should time out the first request'),
rejects(secondRegister, 'Should time out the second request')
]), DEFAULT_TIMEOUT, 'Queued requests did not time out');
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
'Timed out waiting for reconnect');
});
@@ -27,7 +27,8 @@ add_task(function* test_register_rollback() {
let handshakes = 0;
let registers = 0;
let unregisterDefer = Promise.defer();
let unregisterDone;
let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
PushServiceWebSocket._generateID = () => channelID;
PushService.init({
serverURI: "wss://push.example.org/",
@@ -66,7 +67,7 @@ add_task(function* test_register_rollback() {
status: 200,
channelID
}));
unregisterDefer.resolve();
unregisterDone();
}
});
}
@@ -76,14 +77,11 @@ add_task(function* test_register_rollback() {
yield rejects(
PushNotificationService.register('https://example.com/storage-error',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'universe has imploded';
},
'Wrong error for unregister database failure'
'Expected error for unregister database failure'
);
// Should send an out-of-band unregister request.
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Unregister request timed out');
equal(handshakes, 1, 'Wrong handshake count');
equal(registers, 1, 'Wrong register count');
@@ -60,22 +60,14 @@ add_task(function* test_register_success() {
'https://example.org/1',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
);
equal(newRecord.channelID, channelID,
'Wrong channel ID in registration record');
equal(newRecord.pushEndpoint, 'https://example.com/update/1',
'Wrong push endpoint in registration record');
equal(newRecord.scope, 'https://example.org/1',
'Wrong scope in registration record');
equal(newRecord.quota, Infinity,
'Wrong quota in registration record');
let record = yield db.getByKeyID(channelID);
equal(record.channelID, channelID,
'Wrong channel ID in database record');
equal(record.pushEndpoint, 'https://example.com/update/1',
'Wrong push endpoint in database record');
equal(record.scope, 'https://example.org/1',
'Wrong scope in database record');
equal(record.quota, Infinity,
'Wrong quota in database record');
});
@@ -64,15 +64,11 @@ add_task(function* test_pushSubscriptionSuccess() {
var subscriptionUri = serverURL + '/pushSubscriptionSuccesss';
var pushEndpoint = serverURL + '/pushEndpointSuccess';
var pushReceiptEndpoint = serverURL + '/receiptPushEndpointSuccess';
equal(newRecord.subscriptionUri, subscriptionUri,
'Wrong subscription ID in registration record');
equal(newRecord.pushEndpoint, pushEndpoint,
'Wrong push endpoint in registration record');
equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
'Wrong push endpoint receipt in registration record');
equal(newRecord.scope, 'https://example.org/1',
'Wrong scope in registration record');
let record = yield db.getByKeyID(subscriptionUri);
equal(record.subscriptionUri, subscriptionUri,
@@ -107,15 +103,11 @@ add_task(function* test_pushSubscriptionMissingLink2() {
var subscriptionUri = serverURL + '/subscriptionMissingLink2';
var pushEndpoint = serverURL + '/pushEndpointMissingLink2';
var pushReceiptEndpoint = '';
equal(newRecord.subscriptionUri, subscriptionUri,
'Wrong subscription ID in registration record');
equal(newRecord.pushEndpoint, pushEndpoint,
'Wrong push endpoint in registration record');
equal(newRecord.pushReceiptEndpoint, pushReceiptEndpoint,
'Wrong push endpoint receipt in registration record');
equal(newRecord.scope, 'https://example.org/no_receiptEndpoint',
'Wrong scope in registration record');
let record = yield db.getByKeyID(subscriptionUri);
equal(record.subscriptionUri, subscriptionUri,
@@ -22,7 +22,8 @@ function run_test() {
add_task(function* test_register_timeout() {
let handshakes = 0;
let timeoutDefer = Promise.defer();
let timeoutDone;
let timeoutPromise = new Promise(resolve => timeoutDone = resolve);
let registers = 0;
let db = PushServiceWebSocket.newPushDB();
@@ -36,23 +37,14 @@ add_task(function* test_register_timeout() {
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
switch (handshakes) {
case 0:
if (handshakes === 0) {
equal(request.uaid, null, 'Should not include device ID');
deepEqual(request.channelIDs, [],
'Should include empty channel list');
break;
case 1:
} else if (handshakes === 1) {
// Should use the previously-issued device ID when reconnecting,
// but should not include the timed-out channel ID.
equal(request.uaid, userAgentID,
'Should include device ID on reconnect');
deepEqual(request.channelIDs, [],
'Should not include failed channel ID');
break;
default:
} else {
ok(false, 'Unexpected reconnect attempt ' + handshakes);
}
handshakes++;
@@ -74,7 +66,7 @@ add_task(function* test_register_timeout() {
uaid: userAgentID,
pushEndpoint: 'https://example.com/update/timeout',
}));
timeoutDefer.resolve();
timeoutDone();
}, 2000);
registers++;
}
@@ -85,17 +77,14 @@ add_task(function* test_register_timeout() {
yield rejects(
PushNotificationService.register('https://example.net/page/timeout',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for request timeout'
'Expected error for request timeout'
);
let record = yield db.getByKeyID(channelID);
ok(!record, 'Should not store records for timed-out responses');
yield waitForPromise(
timeoutDefer.promise,
timeoutPromise,
DEFAULT_TIMEOUT,
'Reconnect timed out'
);
@@ -25,8 +25,8 @@ function run_test() {
add_task(function* test_register_wrong_id() {
// Should reconnect after the register request times out.
let registers = 0;
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
let helloDone;
let helloPromise = new Promise(resolve => helloDone = after(2, resolve));
PushServiceWebSocket._generateID = () => clientChannelID;
PushService.init({
@@ -61,13 +61,10 @@ add_task(function* test_register_wrong_id() {
yield rejects(
PushNotificationService.register('https://example.com/mismatched',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for mismatched register reply'
'Expected error for mismatched register reply'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
'Reconnect after mismatched register reply timed out');
equal(registers, 1, 'Wrong register count');
});
@@ -21,8 +21,8 @@ function run_test() {
add_task(function* test_register_wrong_type() {
let registers = 0;
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
let helloDone;
let helloPromise = new Promise(resolve => helloDone = after(2, resolve));
PushService._generateID = () => '1234';
PushService.init({
@@ -52,18 +52,13 @@ add_task(function* test_register_wrong_type() {
}
});
let promise =
yield rejects(
PushNotificationService.register('https://example.com/mistyped',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for non-string channel ID'
'Expected error for non-string channel ID'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(helloPromise, DEFAULT_TIMEOUT,
'Reconnect after sending non-string channel ID timed out');
equal(registers, 1, 'Wrong register count');
});
@@ -21,9 +21,6 @@ add_task(function* test_registration_missing_scope() {
});
yield rejects(
PushNotificationService.registration('', ''),
function(error) {
return error.error == 'NotFoundError';
},
'Record missing page and manifest URLs'
);
});
@@ -42,7 +42,8 @@ add_task(function* test_registration_success() {
yield db.put(record);
}
let handshakeDefer = Promise.defer();
let handshakeDone;
let handshakePromise = new Promise(resolve => handshakeDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -50,24 +51,19 @@ add_task(function* test_registration_success() {
return new MockWebSocket(uri, {
onHello(request) {
equal(request.uaid, userAgentID, 'Wrong device ID in handshake');
deepEqual(request.channelIDs.sort(), [
'b1cf38c9-6836-4d29-8a30-a3e98d59b728',
'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b',
'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f',
], 'Wrong channel list in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
handshakeDefer.resolve();
handshakeDone();
}
});
}
});
yield waitForPromise(
handshakeDefer.promise,
handshakePromise,
DEFAULT_TIMEOUT,
'Timed out waiting for handshake'
);
@@ -83,7 +83,6 @@ add_task(function* test1() {
PushService.init({
serverURI: serverURL + "/subscribe",
service: PushServiceHttp2,
db
});
@@ -93,7 +93,6 @@ add_task(function* test1() {
PushService.init({
serverURI: serverURL + "/subscribe",
service: PushServiceHttp2,
db
});
@@ -88,7 +88,6 @@ add_task(function* test1() {
PushService.init({
serverURI: serverURL + "/subscribe",
service: PushServiceHttp2,
db
});
+71
View File
@@ -0,0 +1,71 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = '05f7b940-51b6-4b6f-8032-b83ebb577ded';
function run_test() {
do_get_profile();
setPrefs({
userAgentID: userAgentID,
pingInterval: 10000,
retryBaseInterval: 25,
});
run_next_test();
}
add_task(function* test_ws_retry() {
let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
yield db.put({
channelID: '61770ba9-2d57-4134-b949-d40404630d5b',
pushEndpoint: 'https://example.org/push/1',
scope: 'https://example.net/push/1',
version: 1,
originAttributes: '',
quota: Infinity,
});
let alarmDelays = [];
let setAlarm = PushService.setAlarm;
PushService.setAlarm = function(delay) {
alarmDelays.push(delay);
setAlarm.apply(this, arguments);
};
let handshakeDone;
let handshakePromise = new Promise(resolve => handshakeDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
if (alarmDelays.length == 10) {
PushService.setAlarm = setAlarm;
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID,
}));
handshakeDone();
return;
}
this.serverInterrupt();
},
});
},
});
yield waitForPromise(
handshakePromise,
45000,
'Timed out waiting for successful handshake'
);
deepEqual(alarmDelays, [25, 50, 100, 200, 400, 800, 1600, 3200, 6400, 10000],
'Wrong reconnect alarm delays');
});
@@ -31,9 +31,6 @@ add_task(function* test_unregister_empty_scope() {
yield rejects(
PushNotificationService.unregister('',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })),
function(error) {
return error.error == 'NotFoundError';
},
'Wrong error for empty endpoint'
'Expected error for empty endpoint'
);
});
@@ -25,7 +25,8 @@ add_task(function* test_unregister_error() {
quota: Infinity,
});
let unregisterDefer = Promise.defer();
let unregisterDone;
let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -49,7 +50,7 @@ add_task(function* test_unregister_error() {
error: 'omg, everything is exploding',
channelID
}));
unregisterDefer.resolve();
unregisterDone();
}
});
}
@@ -62,6 +63,6 @@ add_task(function* test_unregister_error() {
ok(!result, 'Deleted push record exists');
// Make sure we send a request to the server.
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});
@@ -39,8 +39,8 @@ add_task(function* test_unregister_invalid_json() {
yield db.put(record);
}
let unregisterDefer = Promise.defer();
let unregisterDone = after(2, unregisterDefer.resolve);
let unregisterDone;
let unregisterPromise = new Promise(resolve => unregisterDone = after(2, resolve));
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -77,6 +77,6 @@ add_task(function* test_unregister_invalid_json() {
ok(!record,
'Failed to delete unregistered record after receiving invalid JSON');
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});
@@ -25,7 +25,8 @@ add_task(function* test_unregister_success() {
quota: Infinity,
});
let unregisterDefer = Promise.defer();
let unregisterDone;
let unregisterPromise = new Promise(resolve => unregisterDone = resolve);
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
@@ -46,7 +47,7 @@ add_task(function* test_unregister_success() {
status: 200,
channelID
}));
unregisterDefer.resolve();
unregisterDone();
}
});
}
@@ -57,6 +58,6 @@ add_task(function* test_unregister_success() {
let record = yield db.getByKeyID(channelID);
ok(!record, 'Unregister did not remove record');
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
yield waitForPromise(unregisterPromise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});
@@ -66,7 +66,6 @@ add_task(function* test1() {
PushService.init({
serverURI: serverURL + "/subscribe",
service: PushServiceHttp2,
db
});
@@ -77,8 +77,7 @@ add_task(function* test_with_data_enabled() {
'https://example.com/page/3',
ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
);
ok(newRecord.p256dhPublicKey, 'Should generate public keys for new records');
ok(newRecord.p256dhPrivateKey, 'Should generate private keys for new records');
ok(newRecord.p256dhKey, 'Should generate public keys for new records');
let record = yield db.getByKeyID('eb18f12a-cc42-4f14-accb-3bfc1227f1aa');
ok(record.p256dhPublicKey, 'Should add public key to partial record');
@@ -1,88 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
const channelIDs = ['0ef2ad4a-6c49-41ad-af6e-95d2425276bf', '4818b54a-97c5-4277-ad5d-0bfe630e4e50'];
var channelIDCounter = 0;
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.org/1'
);
run_next_test();
}
add_task(function* test_webapps_cleardata() {
let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
PushService.init({
serverURI: "wss://push.example.org",
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(data) {
equal(data.messageType, 'hello', 'Handshake: wrong message type');
equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
},
onRegister(data) {
equal(data.messageType, 'register', 'Register: wrong message type');
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID: data.channelID,
uaid: userAgentID,
pushEndpoint: 'https://example.com/update/' + Math.random(),
}));
}
});
}
});
let registers = yield Promise.all([
PushNotificationService.register(
'https://example.org/1',
ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })),
PushNotificationService.register(
'https://example.org/1',
ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })),
]);
Services.obs.notifyObservers(
{ appId: 1, browserOnly: false,
QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])},
"webapps-clear-data", "");
let waitAWhile = new Promise(function(res) {
setTimeout(res, 2000);
});
yield waitAWhile;
let registration;
registration = yield PushNotificationService.registration(
'https://example.org/1',
ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false }));
ok(!registration, 'Registration for { 1, false } should not exist.');
registration = yield PushNotificationService.registration(
'https://example.org/1',
ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true }));
ok(registration, 'Registration for { 1, true } should still exist.');
});
+10 -2
View File
@@ -4,11 +4,18 @@ tail =
# Push notifications and alarms are currently disabled on Android.
skip-if = toolkit == 'android'
[test_clear_origin_data.js]
[test_drop_expired.js]
[test_notification_ack.js]
[test_notification_data.js]
[test_notification_duplicate.js]
[test_notification_error.js]
[test_notification_incomplete.js]
[test_notification_version_string.js]
[test_permissions.js]
run-sequentially = This will delete all existing push subscriptions.
[test_quota_exceeded.js]
[test_quota_observer.js]
[test_register_case.js]
@@ -32,8 +39,9 @@ skip-if = toolkit == 'android'
[test_unregister_invalid_json.js]
[test_unregister_not_found.js]
[test_unregister_success.js]
[test_webapps_cleardata.js]
[test_updateRecordNoEncryptionKeys_ws.js]
[test_reconnect_retry.js]
[test_retry_ws.js]
#http2 test
[test_resubscribe_4xxCode_http2.js]
[test_resubscribe_5xxCode_http2.js]
@@ -60,4 +68,4 @@ skip-if = !hasNode
run-sequentially = node server exceptions dont replay well
[test_clearAll_successful.js]
skip-if = !hasNode
run-sequentially = This will delete all existing push subscritions.
run-sequentially = This will delete all existing push subscriptions.
+2 -1
View File
@@ -447,7 +447,8 @@ nsContentSecurityManager::IsURIPotentiallyTrustworthy(nsIURI* aURI, bool* aIsTru
if (scheme.EqualsLiteral("https") ||
scheme.EqualsLiteral("file") ||
scheme.EqualsLiteral("app")) {
scheme.EqualsLiteral("app") ||
scheme.EqualsLiteral("wss")) {
*aIsTrustWorthy = true;
return NS_OK;
}
+2 -10
View File
@@ -1576,14 +1576,6 @@ this.PushService = {
if (this._UAID)
data["uaid"] = this._UAID;
function sendHelloMessage(ids) {
// On success, ids is an array, on error its not.
data["channelIDs"] = ids.map ?
ids.map(function(el) { return el.channelID; }) : [];
this._wsSendMessage(data);
this._currentState = STATE_WAITING_FOR_HELLO;
}
this._getNetworkState((networkState) => {
if (networkState.ip) {
// Opening an available UDP port.
@@ -1602,8 +1594,8 @@ this.PushService = {
};
}
this._db.getAllChannelIDs(sendHelloMessage.bind(this),
sendHelloMessage.bind(this));
this._wsSendMessage(data);
this._currentState = STATE_WAITING_FOR_HELLO;
});
},
+3 -3
View File
@@ -79,13 +79,13 @@ var tests = [
{ values: "foo bar baz qux", length: 4 } ] }
];
for each (let test in tests) {
for (let test of tests) {
let list = test.element;
for each (let property in test.listProperty.split(".")) {
for (let property of test.listProperty.split(".")) {
list = list[property];
}
for each (let subtest in test.subtests) {
for (let subtest of test.subtests) {
if (subtest.values) {
test.element.setAttribute(test.attribute, subtest.values);
}
@@ -267,7 +267,7 @@ function isValidInterpolation(aFromType, aToType)
// Runs the test.
function run()
{
for each (let additive in [false, true]) {
for (let additive of [false, true]) {
let indexOfExpectedArguments = additive ? 3 : 2;
// Add subtests for each combination of prefix and suffix, and additive
@@ -279,7 +279,7 @@ function run()
toArguments = suffixEntry[1],
expectedArguments = suffixEntry[indexOfExpectedArguments];
for each (let prefixEntry in gPrefixes) {
for (let prefixEntry of gPrefixes) {
let [prefixLength, prefix] = prefixEntry;
addTest(prefixLength, prefix, fromType, fromArguments,
toType, toArguments, toType, expectedArguments, additive);
@@ -298,9 +298,9 @@ function run()
"a", [60, 70, 80, 1, 0, 90, 100], additive);
// Test all pairs of segment types that cannot be interpolated between.
for each (let fromType in gTypes) {
for (let fromType of gTypes) {
let fromArguments = generatePathSegmentArguments(fromType, 0);
for each (let toType in gTypes) {
for (let toType of gTypes) {
if (!isValidInterpolation(fromType, toType)) {
let toArguments = generatePathSegmentArguments(toType, 1000);
addTest(1, "M100,100", fromType, fromArguments,
@@ -314,7 +314,7 @@ function run()
gSVG.setCurrentTime(4);
// Inspect the results of each subtest.
for each (let test in gTests) {
for (let test of gTests) {
let list = test.element.animatedPathSegList;
is(list.numberOfItems, test.prefixLength + 1,
"Length of animatedPathSegList for interpolation " +
@@ -50,7 +50,7 @@ NetworkInterfaceListService.prototype = {
}
};
function FakeNetworkInterface(aAttributes) {
function FakeNetworkInfo(aAttributes) {
this.state = aAttributes.state;
this.type = aAttributes.type;
this.name = aAttributes.name;
@@ -58,11 +58,9 @@ function FakeNetworkInterface(aAttributes) {
this.prefixLengths = aAttributes.prefixLengths;
this.gateways = aAttributes.gateways;
this.dnses = aAttributes.dnses;
this.httpProxyHost = aAttributes.httpProxyHost;
this.httpProxyPort = aAttributes.httpProxyPort;
}
FakeNetworkInterface.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
FakeNetworkInfo.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
getAddresses: function (ips, prefixLengths) {
ips.value = this.ips.slice();
@@ -89,7 +87,7 @@ FakeNetworkInterface.prototype = {
function NetworkInterfaceList (aInterfaceLiterals) {
this._interfaces = [];
for (let entry of aInterfaceLiterals) {
this._interfaces.push(new FakeNetworkInterface(entry));
this._interfaces.push(new FakeNetworkInfo(entry));
}
}
@@ -100,7 +98,7 @@ NetworkInterfaceList.prototype = {
return this._interfaces.length;
},
getInterface: function(index) {
getInterfaceInfo: function(index) {
if (!this._interfaces) {
return null;
}
+53 -9
View File
@@ -35,6 +35,10 @@ XPCOMUtils.defineLazyServiceGetter(this, "gPACGenerator",
"@mozilla.org/pac-generator;1",
"nsIPACGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "gTetheringService",
"@mozilla.org/tethering/service;1",
"nsITetheringService");
const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
const TOPIC_ACTIVE_CHANGED = "network-active-changed";
@@ -99,6 +103,7 @@ function ExtraNetworkInfo(aNetwork) {
this.dnses = aNetwork.info.getDnses();
this.httpProxyHost = aNetwork.httpProxyHost;
this.httpProxyPort = aNetwork.httpProxyPort;
this.mtu = aNetwork.mtu;
}
ExtraNetworkInfo.prototype = {
getAddresses: function(aIps, aPrefixLengths) {
@@ -231,7 +236,9 @@ NetworkManager.prototype = {
let excludeFota = aMsg.json.excludeFota;
let interfaces = [];
for each (let i in this.networkInterfaces) {
for (let key in this.networkInterfaces) {
let network = this.networkInterfaces[key];
let i = network.info;
if ((i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_MMS && excludeMms) ||
(i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_SUPL && excludeSupl) ||
(i.type == Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_IMS && excludeIms) ||
@@ -251,9 +258,7 @@ NetworkManager.prototype = {
ips: ips.value,
prefixLengths: prefixLengths.value,
gateways: i.getGateways(),
dnses: i.getDnses(),
httpProxyHost: i.httpProxyHost,
httpProxyPort: i.httpProxyPort
dnses: i.getDnses()
});
}
return interfaces;
@@ -370,6 +375,13 @@ NetworkManager.prototype = {
return this.setSecondaryDefaultRoute(extNetworkInfo);
})
.then(() => this._addSubnetRoutes(extNetworkInfo))
.then(() => {
if (extNetworkInfo.mtu <= 0) {
return;
}
return this._setMtu(extNetworkInfo);
})
.then(() => this.setAndConfigureActive())
.then(() => {
// Update data connection when Wifi connected/disconnected
@@ -487,6 +499,18 @@ NetworkManager.prototype = {
networkInterfaceLinks: null,
get allNetworkInfo() {
let allNetworkInfo = {};
for (let networkId in this.networkInterfaces) {
if (this.networkInterfaces.hasOwnProperty(networkId)) {
allNetworkInfo[networkId] = this.networkInterfaces[networkId].info;
}
}
return allNetworkInfo;
},
_preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE,
get preferredNetworkType() {
return this._preferredNetworkType;
@@ -802,7 +826,8 @@ NetworkManager.prototype = {
this._activeNetwork = null;
let anyConnected = false;
for each (let network in this.networkInterfaces) {
for (let key in this.networkInterfaces) {
let network = this.networkInterfaces[key];
if (network.info.state != Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
continue;
}
@@ -836,7 +861,9 @@ NetworkManager.prototype = {
}
if (this._manageOfflineStatus) {
Services.io.offline = !anyConnected;
Services.io.offline = !anyConnected &&
(gTetheringService.state ===
Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
}
});
},
@@ -932,6 +959,18 @@ NetworkManager.prototype = {
});
},
_setMtu: function(aNetworkInfo) {
return new Promise((aResolve, aReject) => {
gNetworkService.setMtu(aNetworkInfo.name, aNetworkInfo.mtu, (aSuccess) => {
if (!aSuccess) {
debug("setMtu failed");
}
// Always resolve.
aResolve();
});
});
},
_createNetwork: function(aInterfaceName) {
return new Promise((aResolve, aReject) => {
gNetworkService.createNetwork(aInterfaceName, (aSuccess) => {
@@ -982,13 +1021,18 @@ NetworkManager.prototype = {
});
},
_setDefaultRouteAndProxy: function(aNetwork, aOldInterface) {
_setDefaultRouteAndProxy: function(aNetwork, aOldNetwork) {
if (aOldNetwork) {
return this._removeDefaultRoute(aOldNetwork.info)
.then(() => this._setDefaultRouteAndProxy(aNetwork, null));
}
return new Promise((aResolve, aReject) => {
let networkInfo = aNetwork.info;
let gateways = networkInfo.getGateways();
let oldInterfaceName = (aOldInterface ? aOldInterface.info.name : "");
gNetworkService.setDefaultRoute(networkInfo.name, gateways.length, gateways,
oldInterfaceName, (aSuccess) => {
(aSuccess) => {
if (!aSuccess) {
gNetworkService.destroyNetwork(networkInfo.name, function() {
aReject("setDefaultRoute failed");
+93 -7
View File
@@ -269,6 +269,58 @@ NetworkService.prototype = {
});
},
setNetworkTetheringAlarm(aEnable, aInterface) {
// Method called when enabling disabling tethering, it checks if there is
// some alarm active and move from interfaceAlarm to globalAlarm because
// interfaceAlarm doens't work in tethering scenario due to forwarding.
debug("setNetworkTetheringAlarm for tethering" + aEnable);
let filename = aEnable ? "/proc/net/xt_quota/" + aInterface + "Alert" :
"/proc/net/xt_quota/globalAlert";
let file = new FileUtils.File(filename);
if (!file) {
return;
}
NetUtil.asyncFetch({
uri: NetUtil.newURI(file),
loadUsingSystemPrincipal: true
}, (inputStream, status) => {
if (Components.isSuccessCode(status)) {
let data = NetUtil.readInputStreamToString(inputStream, inputStream.available())
.split("\n");
if (data) {
let threshold = parseInt(data[0], 10);
this._setNetworkTetheringAlarm(aEnable, aInterface, threshold);
}
}
});
},
_setNetworkTetheringAlarm(aEnable, aInterface, aThreshold, aCallback) {
debug("_setNetworkTetheringAlarm for tethering" + aEnable);
let cmd = aEnable ? "setTetheringAlarm" : "removeTetheringAlarm";
let params = {
cmd: cmd,
ifname: aInterface,
threshold: aThreshold,
};
this.controlMessage(params, function(aData) {
let code = aData.resultCode;
let reason = aData.resultReason;
let enableString = aEnable ? "Enable" : "Disable";
debug(enableString + " tethering Alarm result: Code " + code + " reason " + reason);
if (aCallback) {
aCallback.networkUsageAlarmResult(null);
}
});
},
setNetworkInterfaceAlarm: function(aInterfaceName, aThreshold, aCallback) {
if (!aInterfaceName) {
aCallback.networkUsageAlarmResult(-1);
@@ -286,7 +338,26 @@ NetworkService.prototype = {
return
}
self._setNetworkInterfaceAlarm(aInterfaceName, aThreshold, aCallback);
// Check if tethering is enabled
let params = {
cmd: "getTetheringStatus"
};
self.controlMessage(params, function(aResult) {
if (isError(aResult.resultCode)) {
aCallback.networkUsageAlarmResult(aResult.reason);
return;
}
if (aResult.resultReason.indexOf('started') == -1) {
// Tethering disabled, set interfaceAlarm
self._setNetworkInterfaceAlarm(aInterfaceName, aThreshold, aCallback);
return;
}
// Tethering enabled, set globalAlarm
self._setNetworkTetheringAlarm(true, aInterfaceName, aThreshold, aCallback);
});
});
},
@@ -392,14 +463,11 @@ NetworkService.prototype = {
});
},
setDefaultRoute: function(aInterfaceName, aCount, aGateways,
aOldInterfaceName, aCallback) {
setDefaultRoute: function(aInterfaceName, aCount, aGateways, aCallback) {
debug("Going to change default route to " + aInterfaceName);
let options = {
cmd: "setDefaultRoute",
ifname: aInterfaceName,
oldIfname: (aOldInterfaceName && aOldInterfaceName !== aInterfaceName) ?
aOldInterfaceName : null,
gateways: aGateways
};
this.controlMessage(options, function(aResult) {
@@ -545,7 +613,7 @@ NetworkService.prototype = {
aConfig.cmd = "setWifiTethering";
// The callback function in controlMessage may not be fired immediately.
this.controlMessage(aConfig, function(aData) {
this.controlMessage(aConfig, (aData) => {
let code = aData.resultCode;
let reason = aData.resultReason;
let enable = aData.enable;
@@ -553,6 +621,8 @@ NetworkService.prototype = {
debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason);
this.setNetworkTetheringAlarm(aEnable, aConfig.externalIfname);
if (isError(code)) {
aCallback.wifiTetheringEnabledChange("netd command error");
} else {
@@ -565,7 +635,7 @@ NetworkService.prototype = {
setUSBTethering: function(aEnable, aConfig, aCallback) {
aConfig.cmd = "setUSBTethering";
// The callback function in controlMessage may not be fired immediately.
this.controlMessage(aConfig, function(aData) {
this.controlMessage(aConfig, (aData) => {
let code = aData.resultCode;
let reason = aData.resultReason;
let enable = aData.enable;
@@ -573,6 +643,8 @@ NetworkService.prototype = {
debug(enableString + " USB tethering result: Code " + code + " reason " + reason);
this.setNetworkTetheringAlarm(aEnable, aConfig.externalIfname);
if (isError(code)) {
aCallback.usbTetheringEnabledChange("netd command error");
} else {
@@ -729,6 +801,20 @@ NetworkService.prototype = {
});
});
},
setMtu: function (aInterfaceName, aMtu, aCallback) {
debug("Set MTU on " + aInterfaceName + ": " + aMtu);
let params = {
cmd: "setMtu",
ifname: aInterfaceName,
mtu: aMtu
};
this.controlMessage(params, function(aResult) {
aCallback.nativeCommandResult(!aResult.error);
});
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]);
+241 -26
View File
@@ -51,6 +51,11 @@ static const char* USB_FUNCTION_ADB = "adb";
// Use this command to continue the function chain.
static const char* DUMMY_COMMAND = "tether status";
// IPV6 Tethering is not supported in AOSP, use the property to
// identify vendor specific support in IPV6. We can remove this flag
// once upstream Android support IPV6 in tethering.
static const char* IPV6_TETHERING = "ro.tethering.ipv6";
// Retry 20 times (2 seconds) for usb state transition.
static const uint32_t USB_FUNCTION_RETRY_TIMES = 20;
// Check "sys.usb.state" every 100ms.
@@ -81,6 +86,7 @@ static const uint32_t BUF_SIZE = 1024;
static const int32_t SUCCESS = 0;
static uint32_t SDK_VERSION;
static uint32_t SUPPORT_IPV6_TETHERING;
struct IFProperties {
char gateway[PROPERTY_VALUE_MAX];
@@ -201,6 +207,7 @@ const CommandFunc NetworkUtils::sUSBEnableChain[] = {
NetworkUtils::tetheringStatus,
NetworkUtils::startTethering,
NetworkUtils::setDnsForwarders,
NetworkUtils::addUpstreamInterface,
NetworkUtils::usbTetheringSuccess
};
@@ -209,6 +216,7 @@ const CommandFunc NetworkUtils::sUSBDisableChain[] = {
NetworkUtils::removeInterfaceFromLocalNetwork,
NetworkUtils::preTetherInterfaceList,
NetworkUtils::postTetherInterfaceList,
NetworkUtils::removeUpstreamInterface,
NetworkUtils::disableNat,
NetworkUtils::setIpForwardingEnabled,
NetworkUtils::stopTethering,
@@ -223,7 +231,9 @@ const CommandFunc NetworkUtils::sUSBFailChain[] = {
const CommandFunc NetworkUtils::sUpdateUpStreamChain[] = {
NetworkUtils::cleanUpStream,
NetworkUtils::removeUpstreamInterface,
NetworkUtils::createUpStream,
NetworkUtils::addUpstreamInterface,
NetworkUtils::updateUpStreamSuccess
};
@@ -256,6 +266,23 @@ const CommandFunc NetworkUtils::sNetworkInterfaceSetAlarmChain[] = {
NetworkUtils::networkInterfaceAlarmSuccess
};
const CommandFunc NetworkUtils::sTetheringInterfaceSetAlarmChain[] = {
NetworkUtils::setGlobalAlarm,
NetworkUtils::removeAlarm,
NetworkUtils::networkInterfaceAlarmSuccess
};
const CommandFunc NetworkUtils::sTetheringInterfaceRemoveAlarmChain[] = {
NetworkUtils::removeGlobalAlarm,
NetworkUtils::setAlarm,
NetworkUtils::networkInterfaceAlarmSuccess
};
const CommandFunc NetworkUtils::sTetheringGetStatusChain[] = {
NetworkUtils::tetheringStatus,
NetworkUtils::defaultAsyncSuccessHandler
};
/**
* Helper function to get the mask from given prefix length.
*/
@@ -710,6 +737,36 @@ void NetworkUtils::setAlarm(CommandChain* aChain,
doCommand(command, aChain, aCallback);
}
void NetworkUtils::removeAlarm(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeinterfacealert %s", GET_CHAR(mIfname));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::setGlobalAlarm(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setglobalalert %ld", GET_FIELD(mThreshold));
doCommand(command, aChain, aCallback);
}
void NetworkUtils::removeGlobalAlarm(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
PR_snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth removeglobalalert");
doCommand(command, aChain, aCallback);
}
void NetworkUtils::setInterfaceUp(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
@@ -807,12 +864,87 @@ void NetworkUtils::postTetherInterfaceList(CommandChain* aChain,
char buf[BUF_SIZE];
NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
memcpy(buf, reason.get(), reason.Length() + 1);
size_t length = reason.Length() + 1 < BUF_SIZE ? reason.Length() + 1 : BUF_SIZE;
memcpy(buf, reason.get(), length);
split(buf, INTERFACE_DELIMIT, GET_FIELD(mInterfaceList));
doCommand(command, aChain, aCallback);
}
bool isCommandChainIPv6(CommandChain* aChain, const char *externalInterface) {
// Check by gateway address
if (getIpType(GET_CHAR(mGateway)) == AF_INET6) {
return true;
}
uint32_t length = GET_FIELD(mGateways).Length();
for (uint32_t i = 0; i < length; i++) {
NS_ConvertUTF16toUTF8 autoGateway(GET_FIELD(mGateways)[i]);
if(getIpType(autoGateway.get()) == AF_INET6) {
return true;
}
}
// Check by external inteface address
FILE *file = fopen("/proc/net/if_inet6", "r");
if (!file) {
return false;
}
bool isIPv6 = false;
char interface[32];
while(fscanf(file, "%*s %*s %*s %*s %*s %32s", interface)) {
if (strcmp(interface, externalInterface) == 0) {
isIPv6 = true;
break;
}
}
fclose(file);
return isIPv6;
}
void NetworkUtils::addUpstreamInterface(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
nsCString interface(GET_CHAR(mExternalIfname));
if (!interface.get()[0]) {
interface = GET_CHAR(mCurExternalIfname);
}
if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
aCallback(aChain, false, aResult);
return;
}
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface add_upstream %s",
interface.get());
doCommand(command, aChain, aCallback);
}
void NetworkUtils::removeUpstreamInterface(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
nsCString interface(GET_CHAR(mExternalIfname));
if (!interface.get()[0]) {
interface = GET_CHAR(mPreExternalIfname);
}
if (SUPPORT_IPV6_TETHERING == 0 || !isCommandChainIPv6(aChain, interface.get())) {
aCallback(aChain, false, aResult);
return;
}
char command[MAX_COMMAND_SIZE];
snprintf(command, MAX_COMMAND_SIZE - 1, "tether interface remove_upstream %s",
interface.get());
doCommand(command, aChain, aCallback);
}
void NetworkUtils::setIpForwardingEnabled(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
@@ -991,14 +1123,39 @@ void NetworkUtils::removeDefaultRoute(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
// FIXME: (Bug 1121795) We only remove the first gateway to the default route.
// For dual stack (ipv4/ipv6) device, one of the gateway would
// not be added to the default route.
snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s 0.0.0.0/0 %s",
GET_FIELD(mNetId), GET_CHAR(mIfname), GET_CHAR(mGateways[0]));
if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) {
aCallback(aChain, false, aResult);
return;
}
doCommand(command, aChain, aCallback);
char command[MAX_COMMAND_SIZE];
nsTArray<nsString>& gateways = GET_FIELD(mGateways);
NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);
int type = getIpType(autoGateway.get());
snprintf(command, MAX_COMMAND_SIZE - 1, "network route remove %d %s %s/0 %s",
GET_FIELD(mNetId), GET_CHAR(mIfname),
type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
struct MyCallback {
static void callback(CommandCallback::CallbackType aOriginalCallback,
CommandChain* aChain,
bool aError,
mozilla::dom::NetworkResultOptions& aResult)
{
NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
NU_DBG("removeDefaultRoute's reason: %s", reason.get());
if (aError && !reason.EqualsASCII("removeRoute() failed (No such process)")) {
return aOriginalCallback(aChain, aError, aResult);
}
GET_FIELD(mLoopIndex)++;
return removeDefaultRoute(aChain, aOriginalCallback, aResult);
}
};
CommandCallback wrappedCallback(MyCallback::callback, aCallback);
doCommand(command, aChain, wrappedCallback);
}
void NetworkUtils::setInterfaceDns(CommandChain* aChain,
@@ -1146,13 +1303,19 @@ void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
if (GET_FIELD(mLoopIndex) >= GET_FIELD(mGateways).Length()) {
aCallback(aChain, false, aResult);
return;
}
// FIXME: (Bug 1121795) We only add the first gateway to the default route.
// For dual stack (ipv4/ipv6) device, one of the gateway would
// not be added to the default route.
snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s 0.0.0.0/0 %s",
GET_FIELD(mNetId), GET_CHAR(mIfname), GET_CHAR(mGateways[0]));
char command[MAX_COMMAND_SIZE];
nsTArray<nsString>& gateways = GET_FIELD(mGateways);
NS_ConvertUTF16toUTF8 autoGateway(gateways[GET_FIELD(mLoopIndex)]);
int type = getIpType(autoGateway.get());
snprintf(command, MAX_COMMAND_SIZE - 1, "network route add %d %s %s/0 %s",
GET_FIELD(mNetId), GET_CHAR(mIfname),
type == AF_INET6 ? "::" : "0.0.0.0", autoGateway.get());
struct MyCallback {
static void callback(CommandCallback::CallbackType aOriginalCallback,
@@ -1162,11 +1325,12 @@ void NetworkUtils::addDefaultRouteToNetwork(CommandChain* aChain,
{
NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
NU_DBG("addDefaultRouteToNetwork's reason: %s", reason.get());
if (aError && reason.EqualsASCII("addRoute() failed (File exists)")) {
NU_DBG("Ignore \"File exists\" error when adding host route.");
return aOriginalCallback(aChain, false, aResult);
if (aError && !reason.EqualsASCII("addRoute() failed (File exists)")) {
return aOriginalCallback(aChain, aError, aResult);
}
aOriginalCallback(aChain, aError, aResult);
GET_FIELD(mLoopIndex)++;
return addDefaultRouteToNetwork(aChain, aOriginalCallback, aResult);
}
};
@@ -1272,6 +1436,17 @@ void NetworkUtils::disableIpv6(CommandChain* aChain,
setIpv6Enabled(aChain, aCallback, aResult, false);
}
void NetworkUtils::setMtu(CommandChain* aChain,
CommandCallback aCallback,
NetworkResultOptions& aResult)
{
char command[MAX_COMMAND_SIZE];
PR_snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %d",
GET_CHAR(mIfname), GET_FIELD(mMtu));
doCommand(command, aChain, aCallback);
}
#undef GET_CHAR
#undef GET_FIELD
@@ -1450,6 +1625,9 @@ NetworkUtils::NetworkUtils(MessageCallback aCallback)
property_get("ro.build.version.sdk", value, nullptr);
SDK_VERSION = atoi(value);
property_get(IPV6_TETHERING, value, "0");
SUPPORT_IPV6_TETHERING = atoi(value);
gNetworkUtils = this;
}
@@ -1489,6 +1667,9 @@ void NetworkUtils::ExecuteCommand(NetworkParams aOptions)
BUILD_ENTRY(setNetworkInterfaceAlarm),
BUILD_ENTRY(enableNetworkInterfaceAlarm),
BUILD_ENTRY(disableNetworkInterfaceAlarm),
BUILD_ENTRY(setTetheringAlarm),
BUILD_ENTRY(removeTetheringAlarm),
BUILD_ENTRY(getTetheringStatus),
BUILD_ENTRY(setWifiOperationMode),
BUILD_ENTRY(setDhcpServer),
BUILD_ENTRY(setWifiTethering),
@@ -1504,6 +1685,7 @@ void NetworkUtils::ExecuteCommand(NetworkParams aOptions)
BUILD_ENTRY(createNetwork),
BUILD_ENTRY(destroyNetwork),
BUILD_ENTRY(getNetId),
BUILD_ENTRY(setMtu),
#undef BUILD_ENTRY
};
@@ -1822,8 +2004,9 @@ CommandResult NetworkUtils::setDefaultRoute(NetworkParams& aOptions)
}
aOptions.mNetId = netIdInfo.mNetId;
aOptions.mLoopIndex = 0;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
@@ -1834,13 +2017,6 @@ CommandResult NetworkUtils::setDefaultRouteLegacy(NetworkParams& aOptions)
{
NS_ConvertUTF16toUTF8 autoIfname(aOptions.mIfname);
if (!aOptions.mOldIfname.IsEmpty()) {
// Remove IPv4's default route.
RETURN_IF_FAILED(mNetUtils->do_ifc_remove_default_route(GET_CHAR(mOldIfname)));
// Remove IPv6's default route.
WARN_IF_FAILED(mNetUtils->do_ifc_remove_route(GET_CHAR(mOldIfname), "::", 0, NULL));
}
uint32_t length = aOptions.mGateways.Length();
if (length > 0) {
for (uint32_t i = 0; i < length; i++) {
@@ -1916,6 +2092,7 @@ CommandResult NetworkUtils::removeDefaultRoute(NetworkParams& aOptions)
NU_DBG("Obtained netid %d for interface %s", netIdInfo.mNetId, GET_CHAR(mIfname));
aOptions.mNetId = netIdInfo.mNetId;
aOptions.mLoopIndex = 0;
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
@@ -2233,6 +2410,27 @@ CommandResult NetworkUtils::disableNetworkInterfaceAlarm(NetworkParams& aOptions
return CommandResult::Pending();
}
CommandResult NetworkUtils::setTetheringAlarm(NetworkParams& aOptions)
{
NU_DBG("setTetheringAlarm");
runChain(aOptions, sTetheringInterfaceSetAlarmChain, networkInterfaceAlarmFail);
return CommandResult::Pending();
}
CommandResult NetworkUtils::removeTetheringAlarm(NetworkParams& aOptions)
{
NU_DBG("removeTetheringAlarm");
runChain(aOptions, sTetheringInterfaceRemoveAlarmChain, networkInterfaceAlarmFail);
return CommandResult::Pending();
}
CommandResult NetworkUtils::getTetheringStatus(NetworkParams& aOptions)
{
NU_DBG("getTetheringStatus");
runChain(aOptions, sTetheringGetStatusChain, networkInterfaceAlarmFail);
return CommandResult::Pending();
}
/**
* handling main thread's reload Wifi firmware request
*/
@@ -2526,6 +2724,23 @@ CommandResult NetworkUtils::getNetId(NetworkParams& aOptions)
return result;
}
CommandResult NetworkUtils::setMtu(NetworkParams& aOptions)
{
// Setting/getting mtu is supported since Kitkat.
if (SDK_VERSION < 19) {
ERROR("setMtu is not supported in current SDK_VERSION.");
return -1;
}
static CommandFunc COMMAND_CHAIN[] = {
setMtu,
defaultAsyncSuccessHandler,
};
runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
return CommandResult::Pending();
}
void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
{
NetworkResultOptions result;
+19 -4
View File
@@ -110,7 +110,6 @@ public:
COPY_OPT_STRING_FIELD(mIfname, EmptyString())
COPY_OPT_STRING_FIELD(mIp, EmptyString())
COPY_OPT_FIELD(mPrefixLength, 0)
COPY_OPT_STRING_FIELD(mOldIfname, EmptyString())
COPY_OPT_STRING_FIELD(mMode, EmptyString())
COPY_OPT_FIELD(mReport, false)
COPY_OPT_FIELD(mEnabled, false)
@@ -145,6 +144,9 @@ public:
COPY_OPT_FIELD(mGateway_long, 0)
COPY_OPT_FIELD(mDns1_long, 0)
COPY_OPT_FIELD(mDns2_long, 0)
COPY_OPT_FIELD(mMtu, 0)
mLoopIndex = 0;
#undef COPY_SEQUENCE_FIELD
#undef COPY_OPT_STRING_FIELD
@@ -161,7 +163,6 @@ public:
nsString mIfname;
nsString mIp;
uint32_t mPrefixLength;
nsString mOldIfname;
nsString mMode;
bool mReport;
bool mEnabled;
@@ -196,9 +197,11 @@ public:
long mGateway_long;
long mDns1_long;
long mDns2_long;
long mMtu;
// Auxiliary information required to carry accros command chain.
int mNetId; // A locally defined id per interface.
int mNetId; // A locally defined id per interface.
uint32_t mLoopIndex; // Loop index for adding/removing multiple gateways.
};
// CommandChain store the necessary information to execute command one by one.
@@ -299,6 +302,9 @@ private:
CommandResult setNetworkInterfaceAlarm(NetworkParams& aOptions);
CommandResult enableNetworkInterfaceAlarm(NetworkParams& aOptions);
CommandResult disableNetworkInterfaceAlarm(NetworkParams& aOptions);
CommandResult setTetheringAlarm(NetworkParams& aOptions);
CommandResult removeTetheringAlarm(NetworkParams& aOptions);
CommandResult getTetheringStatus(NetworkParams& aOptions);
CommandResult setWifiOperationMode(NetworkParams& aOptions);
CommandResult setDhcpServer(NetworkParams& aOptions);
CommandResult setWifiTethering(NetworkParams& aOptions);
@@ -308,6 +314,7 @@ private:
CommandResult createNetwork(NetworkParams& aOptions);
CommandResult destroyNetwork(NetworkParams& aOptions);
CommandResult getNetId(NetworkParams& aOptions);
CommandResult setMtu(NetworkParams& aOptions);
CommandResult addHostRouteLegacy(NetworkParams& aOptions);
CommandResult removeHostRouteLegacy(NetworkParams& aOptions);
@@ -334,7 +341,9 @@ private:
static const CommandFunc sNetworkInterfaceEnableAlarmChain[];
static const CommandFunc sNetworkInterfaceDisableAlarmChain[];
static const CommandFunc sNetworkInterfaceSetAlarmChain[];
static const CommandFunc sTetheringInterfaceSetAlarmChain[];
static const CommandFunc sTetheringInterfaceRemoveAlarmChain[];
static const CommandFunc sTetheringGetStatusChain[];
/**
* Individual netd command stored in command chain.
*/
@@ -354,12 +363,17 @@ private:
static void setQuota(PARAMS);
static void removeQuota(PARAMS);
static void setAlarm(PARAMS);
static void removeAlarm(PARAMS);
static void setGlobalAlarm(PARAMS);
static void removeGlobalAlarm(PARAMS);
static void setInterfaceUp(PARAMS);
static void tetherInterface(PARAMS);
static void addInterfaceToLocalNetwork(PARAMS);
static void addRouteToLocalNetwork(PARAMS);
static void preTetherInterfaceList(PARAMS);
static void postTetherInterfaceList(PARAMS);
static void addUpstreamInterface(PARAMS);
static void removeUpstreamInterface(PARAMS);
static void setIpForwardingEnabled(PARAMS);
static void tetheringStatus(PARAMS);
static void stopTethering(PARAMS);
@@ -391,6 +405,7 @@ private:
static void modifyRouteOnInterface(PARAMS, bool aDoAdd);
static void enableIpv6(PARAMS);
static void disableIpv6(PARAMS);
static void setMtu(PARAMS);
static void setIpv6Enabled(PARAMS, bool aEnabled);
static void addRouteToSecondaryTable(PARAMS);
static void removeRouteFromSecondaryTable(PARAMS);
+83 -27
View File
@@ -39,12 +39,11 @@ XPCOMUtils.defineLazyGetter(this, "gRil", function() {
return null;
});
const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
const TOPIC_MOZSETTINGS_CHANGED = "mozsettings-changed";
const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
const TOPIC_PREF_CHANGED = "nsPref:changed";
const TOPIC_XPCOM_SHUTDOWN = "xpcom-shutdown";
const PREF_MANAGE_OFFLINE_STATUS = "network.gonk.manage-offline-status";
const PREF_NETWORK_DEBUG_ENABLED = "network.debugging.enabled";
const POSSIBLE_USB_INTERFACE_NAME = "rndis0,usb0";
@@ -126,9 +125,15 @@ function TetheringService() {
Services.obs.addObserver(this, TOPIC_XPCOM_SHUTDOWN, false);
Services.obs.addObserver(this, TOPIC_MOZSETTINGS_CHANGED, false);
Services.obs.addObserver(this, TOPIC_CONNECTION_STATE_CHANGED, false);
Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
Services.prefs.addObserver(PREF_NETWORK_DEBUG_ENABLED, this, false);
Services.prefs.addObserver(PREF_MANAGE_OFFLINE_STATUS, this, false);
try {
this._manageOfflineStatus =
Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
} catch(ex) {
// Ignore.
}
this._dataDefaultServiceId = 0;
@@ -234,6 +239,12 @@ TetheringService.prototype = {
// Arguments for pending wifi tethering request.
_pendingWifiTetheringRequestArgs: null,
// The state of tethering.
state: Ci.nsITetheringService.TETHERING_STATE_INACTIVE,
// Flag to check if we can modify the Services.io.offline.
_manageOfflineStatus: true,
// nsIObserver
observe: function(aSubject, aTopic, aData) {
@@ -257,33 +268,24 @@ TetheringService.prototype = {
" changed state to " + network.state);
this.onConnectionChanged(network);
break;
case TOPIC_INTERFACE_REGISTERED:
network = aSubject.QueryInterface(Ci.nsINetworkInterface);
if (network &&
network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
debug("Force setting " + SETTINGS_DUN_REQUIRED + " to true.");
this.tetheringSettings[SETTINGS_DUN_REQUIRED] = true;
}
break;
case TOPIC_INTERFACE_UNREGISTERED:
network = aSubject.QueryInterface(Ci.nsINetworkInterface);
if (network &&
network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
libcutils.property_get("ro.tethering.dun_required") === "1";
}
break;
case TOPIC_XPCOM_SHUTDOWN:
Services.obs.removeObserver(this, TOPIC_XPCOM_SHUTDOWN);
Services.obs.removeObserver(this, TOPIC_MOZSETTINGS_CHANGED);
Services.obs.removeObserver(this, TOPIC_CONNECTION_STATE_CHANGED);
Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
Services.prefs.removeObserver(PREF_NETWORK_DEBUG_ENABLED, this);
Services.prefs.removeObserver(PREF_MANAGE_OFFLINE_STATUS, this);
this.dunConnectTimer.cancel();
this.dunRetryTimer.cancel();
break;
case PREF_MANAGE_OFFLINE_STATUS:
try {
this._manageOfflineStatus =
Services.prefs.getBoolPref(PREF_MANAGE_OFFLINE_STATUS);
} catch(ex) {
// Ignore.
}
break;
}
},
@@ -361,7 +363,8 @@ TetheringService.prototype = {
},
getNetworkInfo: function(aType, aServiceId) {
for each (let networkInfo in gNetworkManager.allNetworkInfo) {
for (let networkId in gNetworkManager.allNetworkInfo) {
let networkInfo = gNetworkManager.allNetworkInfo[networkId];
if (networkInfo.type == aType) {
try {
if (networkInfo instanceof Ci.nsIRilNetworkInfo) {
@@ -604,10 +607,31 @@ TetheringService.prototype = {
this._wifiTetheringRequestOngoing = true;
gNetworkService.setWifiTethering(aEnable, aConfig, (aError) => {
// Disconnect dun on error or when wifi tethering is disabled.
if (this.tetheringSettings[SETTINGS_DUN_REQUIRED] &&
(!aEnable || aError)) {
this.handleDunConnection(false);
// Change the tethering state to WIFI if there is no error.
if (aEnable && !aError) {
this.state = Ci.nsITetheringService.TETHERING_STATE_WIFI;
} else {
// If wifi thethering is disable, or any error happens,
// then consider the following statements.
// Check whether the state is USB now or not. If no then just change
// it to INACTIVE, if yes then just keep it.
// It means that don't let the disable or error of WIFI affect
// the original active state.
if (this.state != Ci.nsITetheringService.TETHERING_STATE_USB) {
this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
}
// Disconnect dun on error or when wifi tethering is disabled.
if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
this.handleDunConnection(false);
}
}
if (this._manageOfflineStatus) {
Services.io.offline = !this.isAnyConnected() &&
(this.state ===
Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
}
let resetSettings = aError;
@@ -644,6 +668,10 @@ TetheringService.prototype = {
return;
}
// Re-check again, test cases set this property later.
this.tetheringSettings[SETTINGS_DUN_REQUIRED] =
libcutils.property_get("ro.tethering.dun_required") === "1";
if (!aEnable) {
this.enableWifiTethering(false, aConfig, aCallback);
return;
@@ -738,19 +766,36 @@ TetheringService.prototype = {
// Skip others request when we found an error.
this._usbTetheringRequestCount = 0;
this._usbTetheringAction = TETHERING_STATE_IDLE;
// If the thethering state is WIFI now, then just keep it,
// if not, just change the state to INACTIVE.
// It means that don't let the error of USB affect the original active state.
if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) {
this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
}
if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
this.handleDunConnection(false);
}
} else {
if (aEnable) {
this._usbTetheringAction = TETHERING_STATE_ACTIVE;
this.state = Ci.nsITetheringService.TETHERING_STATE_USB;
} else {
this._usbTetheringAction = TETHERING_STATE_IDLE;
// If the state is now WIFI, don't let the disable of USB affect it.
if (this.state != Ci.nsITetheringService.TETHERING_STATE_WIFI) {
this.state = Ci.nsITetheringService.TETHERING_STATE_INACTIVE;
}
if (this.tetheringSettings[SETTINGS_DUN_REQUIRED]) {
this.handleDunConnection(false);
}
}
if (this._manageOfflineStatus) {
Services.io.offline = !this.isAnyConnected() &&
(this.state ===
Ci.nsITetheringService.TETHERING_STATE_INACTIVE);
}
this.handleLastUsbTetheringRequest();
}
},
@@ -830,6 +875,17 @@ TetheringService.prototype = {
callback.call(this);
},
isAnyConnected: function() {
let allNetworkInfo = gNetworkManager.allNetworkInfo;
for (let networkId in allNetworkInfo) {
if (allNetworkInfo.hasOwnProperty(networkId) &&
allNetworkInfo[networkId].state === Ci.nsINetworkInfo.NETWORK_STATE_CONNECTED) {
return true;
}
}
return false;
},
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TetheringService]);
+6 -1
View File
@@ -80,7 +80,7 @@ interface nsINetworkInfo : nsISupports
[array, size_is(count), retval] out wstring dnses);
};
[scriptable, uuid(9a025351-8684-4ab5-a0c1-f21a9f83d405)]
[scriptable, uuid(8b1345fa-b34c-41b3-8d21-09f961bf8887)]
interface nsINetworkInterface : nsISupports
{
/**
@@ -97,4 +97,9 @@ interface nsINetworkInterface : nsISupports
* The port number of the http proxy server.
*/
readonly attribute long httpProxyPort;
/*
* The Maximun Transmit Unit for this network interface, -1 if not set.
*/
readonly attribute long mtu;
};
@@ -2,10 +2,11 @@
* 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/. */
#include "nsINetworkManager.idl"
#include "nsISupports.idl"
[scriptable, uuid(b44d74db-c9d6-41dd-98ae-a56918d6e6ad)]
interface nsINetworkInfo;
[scriptable, uuid(55779d32-1e28-4f43-af87-09d04bc3cce9)]
interface nsINetworkInterfaceList : nsISupports
{
/**
@@ -14,10 +15,10 @@ interface nsINetworkInterfaceList : nsISupports
long getNumberOfInterface();
/**
* Get the i-th interface from the list.
* Get the i-th interface info info from the list.
* @param interfaceIndex index of interface, from 0 to number of interface - 1.
*/
nsINetworkInterface getInterface(in long interfaceIndex);
nsINetworkInfo getInterfaceInfo(in long interfaceIndex);
};
[scriptable, uuid(21d7fc8b-28c4-4a4f-a15e-1f9defbc2cec)]
+25 -14
View File
@@ -157,7 +157,7 @@ interface nsIDhcpRequestCallback : nsISupports
/**
* Provide network services.
*/
[scriptable, uuid(5d6b1877-890a-4c7f-8403-94d57ceba6cf)]
[scriptable, uuid(8f689d9f-30c0-4809-8bf6-87120e71f3ee)]
interface nsINetworkService : nsISupports
{
const long MODIFY_ROUTE_ADD = 0;
@@ -310,8 +310,6 @@ interface nsINetworkService : nsISupports
* Number of elements in gateways.
* @param gateways
* Default gateways for setting default route.
* @param oldInterfaceName
* The previous network interface name.
*
* @param callback
* Callback to notify the result of setting default route.
@@ -319,7 +317,6 @@ interface nsINetworkService : nsISupports
void setDefaultRoute(in DOMString interfaceName,
in unsigned long count,
[array, size_is(count)] in wstring gateways,
in DOMString oldInterfaceName,
in nsINativeCommandCallback callback);
/**
@@ -485,9 +482,9 @@ interface nsINetworkService : nsISupports
in nsINativeCommandCallback callback);
/**
* Reset all connections
* Reset all connections on a specified network interface.
*
* @param networkInterface
* @param interfaceName
* The network interface name which we want to reset.
*
* @param callback
@@ -497,25 +494,25 @@ interface nsINetworkService : nsISupports
in nsINativeCommandCallback callback);
/**
* Create network (required to call prior to any networking operation)
* Create network (required to call prior to any networking operation).
*
* @param networkInterface
* The network interface name which we want to reset.
* @param interfaceName
* The network interface name which we want to create network for.
*
* @param callback
* Callback to notify the result of resetting connections.
* Callback to notify the result of creating network.
*/
void createNetwork(in DOMString interfaceName,
in nsINativeCommandCallback callback);
/**
* Destroy network (required to call prior to any networking operation)
* Destroy network.
*
* @param networkInterface
* The network interface name which we want to reset.
* @param interfaceName
* The network interface name of the network we want to destroy.
*
* @param callback
* Callback to notify the result of resetting connections.
* Callback to notify the result of destroying network.
*/
void destroyNetwork(in DOMString interfaceName,
in nsINativeCommandCallback callback);
@@ -532,4 +529,18 @@ interface nsINetworkService : nsISupports
*
*/
jsval getNetId(in DOMString interfaceName);
/**
* Set maximum transmission unit on a network interface.
*
* @param interfaceName
* The name of the network interface that we want to set mtu.
* @param mtu
* Size of maximum tranmission unit.
*
* @param callback
* Callback to notify the result of setting mtu.
*/
void setMtu(in DOMString interfaceName, in long mtu,
in nsINativeCommandCallback callback);
};
+11 -2
View File
@@ -7,11 +7,20 @@
interface nsINetworkInterface;
interface nsIWifiTetheringCallback;
[scriptable, uuid(993b79df-f10e-4697-a5dc-5981cf8ff7e6)]
[scriptable, uuid(779de2d3-6d29-4ee6-b069-6251839f757a)]
interface nsITetheringService : nsISupports
{
const long TETHERING_STATE_INACTIVE = 0;
const long TETHERING_STATE_WIFI = 1;
const long TETHERING_STATE_USB = 2;
/**
* Enable or disable Wifi Tethering
* Current tethering state. One of the TETHERING_STATE_* constants.
*/
readonly attribute long state;
/**
* Enable or disable Wifi Tethering.
*
* @param enabled
* Boolean that indicates whether tethering should be enabled (true) or

Some files were not shown because too many files have changed in this diff Show More