Bug 1221892 - Extend the debugger protocol to get the matching service worker registration;r=janx

This commit is contained in:
Eddy Bruel 2015-11-13 10:35:50 +01:00
parent 3007e36f63
commit 2e6d9164dd
9 changed files with 294 additions and 2 deletions

View File

@ -25,6 +25,7 @@ support-files =
code_location-changes.js
code_listworkers-worker1.js
code_listworkers-worker2.js
code_getserviceworkerregistration-worker.js
code_math.js
code_math.map
code_math.min.js
@ -112,6 +113,7 @@ support-files =
doc_WorkerActor.attach-tab1.html
doc_WorkerActor.attach-tab2.html
doc_WorkerActor.attachThread-tab.html
getserviceworkerregistration/doc_getserviceworkerregistration-tab.html
head.js
sjs_random-javascript.sjs
testactors.js
@ -219,6 +221,7 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_console-named-eval.js]
skip-if = e10s && debug
[browser_dbg_getserviceworkerregistration.js]
[browser_dbg_server-conditional-bp-01.js]
skip-if = e10s && debug
[browser_dbg_server-conditional-bp-02.js]

View File

@ -0,0 +1,73 @@
var TAB_URL = EXAMPLE_URL + "getserviceworkerregistration/doc_getserviceworkerregistration-tab.html";
var WORKER_URL = "../code_getserviceworkerregistration-worker.js";
var SCOPE1_URL = EXAMPLE_URL;
var SCOPE2_URL = EXAMPLE_URL + "getserviceworkerregistration/";
function test() {
SpecialPowers.pushPrefEnv({'set': [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true],
]}, function () {
Task.spawn(function* () {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
let client = new DebuggerClient(DebuggerServer.connectPipe());
yield connect(client);
let tab = yield addTab(TAB_URL);
let { tabs } = yield listTabs(client);
let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL));
info("Check that the getting service worker registration is initially " +
"empty.");
let { registration } = yield getServiceWorkerRegistration(tabClient);
is(registration, null);
info("Register a service worker in the same scope as the page, and " +
"check that it becomes the current service worker registration.");
executeSoon(() => {
evalInTab(tab, "promise1 = navigator.serviceWorker.register('" +
WORKER_URL + "', { scope: '" + SCOPE1_URL + "' });");
});
yield waitForServiceWorkerRegistrationChanged(tabClient);
({ registration } = yield getServiceWorkerRegistration(tabClient));
is(registration.scope, SCOPE1_URL);
info("Register a second service worker with a more specific scope, and " +
"check that it becomes the current service worker registration.");
executeSoon(() => {
evalInTab(tab, "promise2 = promise1.then(function () { " +
"return navigator.serviceWorker.register('" +
WORKER_URL + "', { scope: '" + SCOPE2_URL + "' }); });");
});
yield waitForServiceWorkerRegistrationChanged(tabClient);
({ registration } = yield getServiceWorkerRegistration(tabClient));
is(registration.scope, SCOPE2_URL);
info("Unregister the second service worker, and check that the " +
"first service worker becomes the current service worker " +
"registration again.");
executeSoon(() => {
evalInTab(tab, "promise2.then(function (registration) { " +
"registration.unregister(); });")
});
yield waitForServiceWorkerRegistrationChanged(tabClient);
({ registration } = yield getServiceWorkerRegistration(tabClient));
is(registration.scope, SCOPE1_URL);
info("Unregister the first service worker, and check that the current " +
"service worker registration becomes empty again.");
executeSoon(() => {
evalInTab(tab, "promise1.then(function (registration) { " +
"registration.unregister(); });");
});
yield waitForServiceWorkerRegistrationChanged(tabClient);
({ registration } = yield getServiceWorkerRegistration(tabClient));
is(registration, null);
yield close(client);
finish();
});
});
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
</head>
<body>
<!-- This page is deliberately in a subdirectory, so we can register a
service worker which scope is less specific than the page itself. -->
</body>
</html>

View File

@ -1066,6 +1066,15 @@ function listWorkers(tabClient) {
});
}
function getServiceWorkerRegistration(tabClient) {
info("Getting current service worker registration.");
return new Promise(function (resolve) {
tabClient.getServiceWorkerRegistration(function (response) {
resolve(response);
});
});
}
function findWorker(workers, url) {
info("Finding worker with url '" + url + "'.");
for (let worker of workers) {
@ -1095,6 +1104,16 @@ function waitForWorkerListChanged(tabClient) {
});
}
function waitForServiceWorkerRegistrationChanged(tabClient) {
info("Waiting for current service worker registration to change.");
return new Promise(function (resolve) {
tabClient.addListener("serviceWorkerRegistrationChanged", function listener() {
tabClient.removeListener("serviceWorkerRegistrationChanged", listener);
resolve();
});
});
}
function attachThread(workerClient, options) {
info("Attaching to thread.");
return new Promise(function(resolve, reject) {

View File

@ -18,11 +18,18 @@ var makeDebugger = require("./utils/make-debugger");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(
this, "swm",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager"
);
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActor", "devtools/server/actors/worker", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
// Assumptions on events module:
@ -112,6 +119,34 @@ function sendShutdownEvent() {
exports.sendShutdownEvent = sendShutdownEvent;
/**
* Returns the service worker registration that matches the given client URL, or
* null if no such registration exists.
*
* The service worker specification defines the service worker registration that
* matches a given client URL as the registration which scope is the longest
* prefix of the client URL (see section 9.15 for details).
*/
function matchServiceWorkerRegistration(clientURL) {
let matchingRegistration = null;
let array = swm.getAllRegistrations();
for (let index = 0; index < array.length; ++index) {
let registration =
array.queryElementAt(index, Ci.nsIServiceWorkerRegistrationInfo);
if (clientURL.indexOf(registration.scope) !== 0) {
continue;
}
if (matchingRegistration === null ||
matchingRegistration.scope.length < registration.scope.length) {
matchingRegistration = registration;
}
}
return matchingRegistration;
}
/**
* Construct a root actor appropriate for use in a server running in a
* browser. The returned root actor:
@ -739,6 +774,9 @@ function TabActor(aConnection)
this._workerActorList = null;
this._workerActorPool = null;
this._onWorkerActorListChanged = this._onWorkerActorListChanged.bind(this);
this._serviceWorkerRegistrationActor = null;
this._mustNotifyServiceWorkerRegistrationChanged = false;
}
// XXX (bug 710213): TabActor attach/detach/exit/disconnect is a
@ -1121,6 +1159,107 @@ TabActor.prototype = {
this.conn.sendActorEvent(this.actorID, "workerListChanged");
},
/**
* Gets the current service worker registration for this tab. The current
* service worker registration is the registration which scope URL forms the
* longest prefix of the URL of the current page in the tab (see
* matchServiceWorkerRegistration for details).
*
* This request works similar to a live list, in the sense that it will send
* a one-shot notification when the current service worker registration
* changes, provided the client has sent this request at least once since the
* last notification was sent.
*/
onGetServiceWorkerRegistration: function () {
// The actor for the current service worker registration. This will
// initially be set to null. Whenever possible, we will reuse the actor for
// the previous service worker registration. Otherwise, a new actor for the
// current service worker registration will be created, provided such a
// registration exists.
let actor = null;
// Get the current service worker registration and compare it against the
// previous service worker registration.
//
// Note that we can obtain the previous service worker registration from the
// actor for the previous service worker registration. If no such actor
// exists, the previous service registration was null.
let registration = matchServiceWorkerRegistration(this.url);
if ((this._serviceWorkerRegistrationActor === null &&
registration === null) ||
(this._serviceWorkerRegistrationActor !== null &&
this._serviceWorkerRegistrationActor.registration === registration)) {
// The previous service worker registration equals the current service
// worker registration, so reuse the actor for the previous service
// worker registration.
actor = this._serviceWorkerRegistrationActor;
}
else {
// The previous service worker registration does not equal the current
// service worker registration, so remove the actor for the previous
// service worker registration from the tab actor pool (this will cause
// the actor to be destroyed).
if (this._serviceWorkerRegistrationActor) {
this._tabPool.removeActor(this._serviceWorkerRegistrationActor);
}
// If there is no service worker registration that matches the URL of the
// page in the tab, the current service worker registration will be null.
// In that case, no actor should be created.
if (registration !== null) {
// Create a new actor for the current service worker registration, and
// add it to the tab actor pool. We use the tab actor pool because we
// don't want the actor to persist when we detach from the tab.
actor = new ServiceWorkerRegistrationActor(registration);
this._tabPool.addActor(actor);
}
// Cache the actor for the current service worker registration. On
// subsequent requests, this will become the actor for the previous
// service worker registration.
this._serviceWorkerRegistrationActor = actor;
}
// Make sure we send a one-shot notification when the current service worker
// registration changes.
if (!this._mustNotifyServiceWorkerRegistrationChanged) {
swm.addListener(this);
this._mustNotifyServiceWorkerRegistrationChanged = true;
}
// Return the actor for the current service worker registration, or null if
// no such registration exists.
return {
"registration": actor !== null ? actor.form() : null
}
},
_notifyServiceWorkerRegistrationChanged: function () {
this.conn.sendActorEvent(this.actorID, "serviceWorkerRegistrationChanged");
swm.removeListener(this);
this._mustNotifyServiceWorkerRegistrationChanged = false;
},
onRegister: function () {
let registration = matchServiceWorkerRegistration(this.url);
if ((this._serviceWorkerRegistrationActor === null &&
registration !== null) ||
(this._serviceWorkerRegistrationActor !== null &&
this._serviceWorkerRegistrationActor.registration !== registration)) {
this._notifyServiceWorkerRegistrationChanged();
}
},
onUnregister: function () {
let registration = matchServiceWorkerRegistration(this.url);
if ((this._serviceWorkerRegistrationActor === null &&
registration !== null) ||
(this._serviceWorkerRegistrationActor !== null &&
this._serviceWorkerRegistrationActor.registration !== registration)) {
this._notifyServiceWorkerRegistrationChanged();
}
},
observe: function (aSubject, aTopic, aData) {
// Ignore any event that comes before/after the tab actor is attached
// That typically happens during firefox shutdown.
@ -1840,7 +1979,8 @@ TabActor.prototype.requestTypes = {
"reconfigure": TabActor.prototype.onReconfigure,
"switchToFrame": TabActor.prototype.onSwitchToFrame,
"listFrames": TabActor.prototype.onListFrames,
"listWorkers": TabActor.prototype.onListWorkers
"listWorkers": TabActor.prototype.onListWorkers,
"getServiceWorkerRegistration": TabActor.prototype.onGetServiceWorkerRegistration
};
exports.TabActor = TabActor;

View File

@ -220,3 +220,24 @@ WorkerActorList.prototype = {
};
exports.WorkerActorList = WorkerActorList;
function ServiceWorkerRegistrationActor(registration) {
this._registration = registration;
}
ServiceWorkerRegistrationActor.prototype = {
get registration() {
return this._registration;
},
actorPrefix: "serviceWorkerRegistration",
form: function () {
return {
actor: this.actorID,
scope: this._registration.scope
};
}
};
exports.ServiceWorkerRegistrationActor = ServiceWorkerRegistrationActor;

View File

@ -160,6 +160,7 @@ const UnsolicitedNotifications = {
"reflowActivity": "reflowActivity",
"addonListChanged": "addonListChanged",
"workerListChanged": "workerListChanged",
"serviceWorkerRegistrationChanged": "serviceWorkerRegistrationChanged",
"tabNavigated": "tabNavigated",
"frameUpdate": "frameUpdate",
"pageError": "pageError",
@ -1226,7 +1227,7 @@ function TabClient(aClient, aForm) {
this.thread = null;
this.request = this.client.request;
this.traits = aForm.traits || {};
this.events = ["workerListChanged"];
this.events = ["workerListChanged", "serviceWorkerRegistrationChanged"];
}
TabClient.prototype = {
@ -1336,6 +1337,12 @@ TabClient.prototype = {
telemetry: "LISTWORKERS"
}),
getServiceWorkerRegistration: DebuggerClient.requester({
type: "getServiceWorkerRegistration",
}, {
telemetry: "GETSERVICEWORKERREGISTRATION"
}),
attachWorker: function (aWorkerActor, aOnResponse) {
this.client.attachWorker(aWorkerActor, aOnResponse);
}

View File

@ -6133,6 +6133,24 @@
"n_buckets": "50",
"description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_GETSERVICEWORKERREGISTRATION_MS": {
"alert_emails": ["dev-developer-tools@lists.mozilla.org", "ejpbruel@mozilla.com"],
"expires_in_version": "50",
"kind": "exponential",
"high": "10000",
"n_buckets": "50",
"bug_numbers": [1221892],
"description": "The time (in milliseconds) that it took a 'getServiceWorkerRegistration' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_REMOTE_GETSERVICEWORKERREGISTRATION_MS": {
"alert_emails": ["dev-developer-tools@lists.mozilla.org", "ejpbruel@mozilla.com"],
"expires_in_version": "50",
"kind": "exponential",
"high": "10000",
"n_buckets": "50",
"bug_numbers": [1221892],
"description": "The time (in milliseconds) that it took a 'getServiceWorkerRegistration' request to go round trip."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTPROCESSES_MS": {
"expires_in_version": "never",
"kind": "exponential",