Bug 1222617 - Filter out service worker messages that happened before a page load;r=bkelly
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -730,19 +730,30 @@ WebConsoleActor.prototype =
while (types.length > 0) {
let type = types.shift();
switch (type) {
case "ConsoleAPI": {
if (!this.consoleAPIListener) {
break;
}
+
+ let requestStartTime = this.window ?
+ this.window.performance.timing.requestStart : 0;
+
let cache = this.consoleAPIListener
.getCachedMessages(!this.parentActor.isRootActor);
cache.forEach((aMessage) => {
+ // Filter out messages that came from a ServiceWorker but happened
+ // before the page was requested.
+ if (aMessage.innerID === "ServiceWorker" &&
+ requestStartTime > aMessage.timeStamp) {
+ return;
+ }
+
let message = this.prepareConsoleMessageForRemote(aMessage);
message._type = type;
messages.push(message);
});
break;
}
case "PageError": {
if (!this.consoleServiceListener) {
--- a/devtools/shared/webconsole/test/chrome.ini
+++ b/devtools/shared/webconsole/test/chrome.ini
@@ -13,16 +13,17 @@ support-files =
[test_basics.html]
[test_bug819670_getter_throws.html]
[test_cached_messages.html]
[test_commands_other.html]
[test_commands_registration.html]
[test_consoleapi.html]
[test_consoleapi_innerID.html]
[test_console_serviceworker.html]
+[test_console_serviceworker_cached.html]
[test_console_styling.html]
[test_file_uri.html]
[test_reflow.html]
[test_jsterm.html]
[test_jsterm_autocomplete.html]
[test_jsterm_cd_iframe.html]
[test_jsterm_last_result.html]
[test_jsterm_queryselector.html]
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -128,16 +128,26 @@ function _attachConsole(aListeners, aCal
function closeDebugger(aState, aCallback)
{
aState.dbgClient.close(aCallback);
aState.dbgClient = null;
aState.client = null;
}
+function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls)
+{
+ is(consoleCalls.length, expectedConsoleCalls.length,
+ 'received correct number of console calls');
+ expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+ info("checking received console call #" + aIndex);
+ checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+ });
+}
+
function checkConsoleAPICall(aCall, aExpected)
{
if (aExpected.level != "trace" && aExpected.arguments) {
is(aCall.arguments.length, aExpected.arguments.length,
"number of arguments");
}
checkObject(aCall, aExpected);
@@ -238,8 +248,81 @@ function runTests(aTests, aEndCallback)
gTestState.driver = driver();
return gTestState.driver.next();
}
function nextTest(aMessage)
{
return gTestState.driver.next(aMessage);
}
+
+function withFrame(url) {
+ return new Promise(resolve => {
+ let iframe = document.createElement("iframe");
+ iframe.onload = function() {
+ resolve(iframe);
+ };
+ iframe.src = url;
+ document.body.appendChild(iframe);
+ });
+}
+
+function navigateFrame(iframe, url) {
+ return new Promise(resolve => {
+ iframe.onload = function() {
+ resolve(iframe);
+ };
+ iframe.src = url;
+ });
+}
+
+function forceReloadFrame(iframe) {
+ return new Promise(resolve => {
+ iframe.onload = function() {
+ resolve(iframe);
+ };
+ iframe.contentWindow.location.reload(true);
+ });
+}
+
+function withActiveServiceWorker(win, url, scope) {
+ let opts = {};
+ if (scope) {
+ opts.scope = scope;
+ }
+ return win.navigator.serviceWorker.register(url, opts).then(swr => {
+ if (swr.active) {
+ return swr;
+ }
+
+ // Unfortunately we can't just use navigator.serviceWorker.ready promise
+ // here. If the service worker is for a scope that does not cover the window
+ // then the ready promise will never resolve. Instead monitor the service
+ // workers state change events to determine when its activated.
+ return new Promise(resolve => {
+ let sw = swr.waiting || swr.installing;
+ sw.addEventListener('statechange', function stateHandler(evt) {
+ if (sw.state === 'activated') {
+ sw.removeEventListener('statechange', stateHandler);
+ resolve(swr);
+ }
+ });
+ });
+ });
+}
+
+function messageServiceWorker(win, scope, message) {
+ return win.navigator.serviceWorker.getRegistration(scope).then(swr => {
+ return new Promise(resolve => {
+ win.navigator.serviceWorker.onmessage = evt => {
+ resolve();
+ };
+ let sw = swr.active || swr.waiting || swr.installing;
+ sw.postMessage({ type: 'PING', message: message });
+ });
+ })
+}
+
+function unregisterServiceWorker(win) {
+ return win.navigator.serviceWorker.ready.then(swr => {
+ return swr.unregister();
+ });
+}
--- a/devtools/shared/webconsole/test/test_console_serviceworker.html
+++ b/devtools/shared/webconsole/test/test_console_serviceworker.html
@@ -59,85 +59,16 @@ let startTest = Task.async(function*() {
["devtools.webconsole.filter.serviceworkers", true]
]}, resolve);
});
attachConsoleToTab(["ConsoleAPI"], onAttach);
});
addEventListener("load", startTest);
-function withFrame(url) {
- return new Promise(resolve => {
- let iframe = document.createElement("iframe");
- iframe.onload = function() {
- resolve(iframe);
- };
- iframe.src = url;
- document.body.appendChild(iframe);
- });
-}
-
-function navigateFrame(iframe, url) {
- return new Promise(resolve => {
- iframe.onload = function() {
- resolve(iframe);
- };
- iframe.src = url;
- });
-}
-
-function forceReloadFrame(iframe) {
- return new Promise(resolve => {
- iframe.onload = function() {
- resolve(iframe);
- };
- iframe.contentWindow.location.reload(true);
- });
-}
-
-function withActiveServiceWorker(win, url, scope) {
- return win.navigator.serviceWorker.register(url, { scope: scope }).then(swr => {
- if (swr.active) {
- return swr;
- }
-
- // Unfortunately we can't just use navigator.serviceWorker.ready promise
- // here. If the service worker is for a scope that does not cover the window
- // then the ready promise will never resolve. Instead monitor the service
- // workers state change events to determine when its activated.
- return new Promise(resolve => {
- let sw = swr.waiting || swr.installing;
- sw.addEventListener('statechange', function stateHandler(evt) {
- if (sw.state === 'activated') {
- sw.removeEventListener('statechange', stateHandler);
- resolve(swr);
- }
- });
- });
- });
-}
-
-function messageServiceWorker(win, scope, message) {
- return win.navigator.serviceWorker.getRegistration(scope).then(swr => {
- return new Promise(resolve => {
- win.navigator.serviceWorker.onmessage = evt => {
- resolve();
- };
- let sw = swr.active || swr.waiting || swr.installing;
- sw.postMessage({ type: 'PING', message: message });
- });
- })
-}
-
-function unregisterServiceWorker(win) {
- return win.navigator.serviceWorker.ready.then(swr => {
- return swr.unregister();
- });
-}
-
let onAttach = Task.async(function*(state, response) {
onConsoleAPICall = onConsoleAPICall.bind(null, state);
state.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
let currentFrame;
try {
// First, we need a frame from which to register our script. This
// will not trigger any console calls.
@@ -189,22 +120,17 @@ let onAttach = Task.async(function*(stat
info("Completed force refresh. Messaging service worker.");
yield messageServiceWorker(currentFrame.contentWindow, SCOPE, MESSAGE);
info("Done messaging service worker. Unregistering service worker.");
yield unregisterServiceWorker(currentFrame.contentWindow);
info('Service worker unregistered. Checking console calls.');
state.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
- is(consoleCalls.length, expectedConsoleCalls.length,
- 'received correct number of console calls');
- expectedConsoleCalls.forEach(function(aMessage, aIndex) {
- info("checking received console call #" + aIndex);
- checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
- });
+ checkConsoleAPICalls(consoleCalls, expectedConsoleCalls);
} catch(error) {
ok(false, 'unexpected error: ' + error);
} finally {
if (currentFrame) {
currentFrame.remove();
currentFrame = null;
}
consoleCalls = [];
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_console_serviceworker_cached.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for getCachedMessages and Service Workers</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for getCachedMessages and Service Workers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let BASE_URL = "https://example.com/chrome/devtools/shared/webconsole/test/";
+let SERVICE_WORKER_URL = BASE_URL + "helper_serviceworker.js";
+let FRAME_URL = BASE_URL + "sandboxed_iframe.html";
+
+let firstTabExpectedCalls = [
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['script evaluation'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['install event'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['activate event'],
+ },
+];
+
+let secondTabExpectedCalls = [
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['fetch event: ' + FRAME_URL],
+ }
+];
+
+let startTest = Task.async(function*() {
+ removeEventListener("load", startTest);
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["devtools.webconsole.filter.serviceworkers", true]
+ ]}, resolve);
+ });
+
+ info("Adding a tab and attaching a service worker");
+ let tab1 = yield addTab(FRAME_URL);
+ let swr = yield withActiveServiceWorker(tab1.linkedBrowser.contentWindow,
+ SERVICE_WORKER_URL);
+
+ yield new Promise(resolve => {
+ info("Attaching console to tab 1");
+ attachConsoleToTab(["ConsoleAPI"], function(state) {
+ state.client.getCachedMessages(["ConsoleAPI"], function(calls) {
+ checkConsoleAPICalls(calls.messages, firstTabExpectedCalls);
+ closeDebugger(state, resolve);
+ });
+ });
+ });
+
+ // Because this tab is being added after the original messages happened,
+ // they shouldn't show up in a call to getCachedMessages.
+ // However, there is a fetch event which is logged due to loading the tab.
+ info("Adding a new tab at the same URL");
+ let tab2 = yield addTab(FRAME_URL);
+ yield new Promise(resolve => {
+ info("Attaching console to tab 2");
+ attachConsoleToTab(["ConsoleAPI"], function(state) {
+ state.client.getCachedMessages(["ConsoleAPI"], function(calls) {
+ checkConsoleAPICalls(calls.messages, secondTabExpectedCalls);
+ closeDebugger(state, resolve);
+ });
+ });
+ });
+
+ yield swr.unregister();
+
+ SimpleTest.finish();
+});
+addEventListener("load", startTest);
+
+// This test needs to add tabs that are controlled by a service worker
+// so use some special powers to dig around and find gBrowser
+let {gBrowser} = SpecialPowers._getTopChromeWindow(SpecialPowers.window.get());
+
+SimpleTest.registerCleanupFunction(() => {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function addTab(url) {
+ info("Adding a new tab with URL: '" + url + "'");
+ return new Promise(resolve => {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ info("URL '" + url + "' loading complete");
+ resolve(tab);
+ }, true);
+ });
+}
+
+</script>
+</body>
+</html>