Skip to content

Commit

Permalink
Bug 1210211 - Part 1: Delay updating push quota. r=kitcambridge
Browse files Browse the repository at this point in the history
--HG--
extra : commitid : GXDHaPbe8pi
extra : rebase_source : 260a2a8e1434355671b25d58e36007c88b934464
  • Loading branch information
William Chen committed Nov 16, 2015
1 parent 96905fb commit e791f70
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 20 deletions.
23 changes: 11 additions & 12 deletions dom/push/PushRecord.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,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);
}
},

Expand All @@ -93,6 +83,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.
Expand Down
92 changes: 85 additions & 7 deletions dom/push/PushService.jsm
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const prefs = new Preferences("dom.push.");

const kCHILD_PROCESS_MESSAGES = ["Push:Register", "Push:Unregister",
"Push:Registration", "Push:RegisterEventNotificationListener",
"Push:NotificationForOriginShown",
"Push:NotificationForOriginClosed",
"child-process-shutdown"];

const PUSH_SERVICE_UNINIT = 0;
Expand Down Expand Up @@ -97,6 +99,11 @@ this.PushService = {
_db: null,
_options: null,
_alarmID: null,
_visibleNotifications: new Map(),

// Callback that is called after attempting to
// reduce the quota for a record. Used for testing purposes.
_updateQuotaTestCallback: null,

_childListeners: [],

Expand Down Expand Up @@ -883,20 +890,77 @@ this.PushService = {
if (shouldNotify) {
notified = this._notifyApp(record, message);
}
if (record.isExpired()) {
this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
// Drop the registration in the background. If the user returns to the
// site, the service worker will be notified on the next `idle-daily`
// event.
this._backgroundUnregister(record);
}
// Update quota after the delay, at which point
// we check for visible notifications.
setTimeout(() => this._updateQuota(keyID),
prefs.get("quotaUpdateDelay"));
return notified;
});
}).catch(error => {
console.error("receivedPushMessage: Error notifying app", error);
});
},

_updateQuota: function(keyID) {
console.debug("updateQuota()");

this._db.update(keyID, record => {
// Record may have expired from an earlier quota update.
if (record.isExpired()) {
console.debug(
"updateQuota: Trying to update quota for expired record", record);
return null;
}
// If there are visible notifications, don't apply the quota penalty
// for the message.
if (!this._visibleNotifications.has(record.uri.prePath)) {
record.reduceQuota();
}
return record;
}).then(record => {
if (record && record.isExpired()) {
this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
// Drop the registration in the background. If the user returns to the
// site, the service worker will be notified on the next `idle-daily`
// event.
this._backgroundUnregister(record);
}
if (this._updateQuotaTestCallback) {
// Callback so that test may be notified when the quota update is complete.
this._updateQuotaTestCallback();
}
}).catch(error => {
console.debug("updateQuota: Error while trying to update quota", error);
});
},

_notificationForOriginShown(origin) {
console.debug("notificationForOriginShown()", origin);
let count;
if (this._visibleNotifications.has(origin)) {
count = this._visibleNotifications.get(origin);
} else {
count = 0;
}
this._visibleNotifications.set(origin, count + 1);
},

_notificationForOriginClosed(origin) {
console.debug("notificationForOriginClosed()", origin);
let count;
if (this._visibleNotifications.has(origin)) {
count = this._visibleNotifications.get(origin);
} else {
console.debug("notificationForOriginClosed: closing notification that has not been shown?");
return;
}
if (count > 1) {
this._visibleNotifications.set(origin, count - 1);
} else {
this._visibleNotifications.delete(origin);
}
},

_notifyApp: function(aPushRecord, message) {
if (!aPushRecord || !aPushRecord.scope ||
aPushRecord.originAttributes === undefined) {
Expand Down Expand Up @@ -1055,6 +1119,20 @@ this.PushService = {
this._childListeners.splice(i, 1);
}
}
console.debug("receiveMessage: Clearing notifications from child");
this._visibleNotifications.clear();
return;
}

if (aMessage.name === "Push:NotificationForOriginShown") {
console.debug("receiveMessage: Notification shown from child");
this._notificationForOriginShown(aMessage.data);
return;
}

if (aMessage.name === "Push:NotificationForOriginClosed") {
console.debug("receiveMessage: Notification closed from child");
this._notificationForOriginClosed(aMessage.data);
return;
}

Expand Down
1 change: 1 addition & 0 deletions dom/push/test/xpcshell/head.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,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]);
Expand Down
6 changes: 5 additions & 1 deletion modules/libpref/init/all.js
Original file line number Diff line number Diff line change
Expand Up @@ -4501,10 +4501,14 @@ pref("dom.push.loglevel", "off");
pref("dom.push.serverURL", "wss://push.services.mozilla.com/");
pref("dom.push.userAgentID", "");

// The maximum number of notifications that a service worker can receive
// The maximum number of push messages that a service worker can receive
// without user interaction.
pref("dom.push.maxQuotaPerSubscription", 16);

// The delay between receiving a push message and updating the quota for a
// subscription.
pref("dom.push.quotaUpdateDelay", 3000); // 3 seconds

// Is the network connection allowed to be up?
// This preference should be used in UX to enable/disable push.
pref("dom.push.connection.enabled", true);
Expand Down

0 comments on commit e791f70

Please sign in to comment.