Bug 1222617 - Filter out service worker messages that happened before a page load;r=bkelly
authorBrian Grinstead <bgrinstead@mozilla.com>
Fri, 20 Nov 2015 06:52:32 -0800
changeset 273575 8cab032fa1e931b6eb294000cb8af0b2393dff0c
parent 273574 5b8f767ea1f84dd760660738e6b0693a643e20df
child 273576 403e24f367aec778c7b3b5fbf8025bc25bde0540
push id29706
push userkwierso@gmail.com
push dateFri, 20 Nov 2015 22:41:51 +0000
treeherdermozilla-central@3f5afaf4e6b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbkelly
bugs1222617
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1222617 - Filter out service worker messages that happened before a page load;r=bkelly
devtools/server/actors/webconsole.js
devtools/shared/webconsole/test/chrome.ini
devtools/shared/webconsole/test/common.js
devtools/shared/webconsole/test/test_console_serviceworker.html
devtools/shared/webconsole/test/test_console_serviceworker_cached.html
--- 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>