Bug 1213932 - Restrict ServiceWorker logging to windows that match navigator.serviceWorker.controller.scriptURL;r=baku,r=past
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1529,18 +1529,17 @@ WebConsoleActor.prototype =
* @return object
* The object that can be sent to the remote client.
*/
prepareConsoleMessageForRemote:
function WCA_prepareConsoleMessageForRemote(aMessage, aUseObjectGlobal = true)
{
let result = WebConsoleUtils.cloneObject(aMessage);
- result.workerType = CONSOLE_WORKER_IDS.indexOf(result.innerID) == -1
- ? 'none' : result.innerID;
+ result.workerType = WebConsoleUtils.getWorkerType(result) || "none";
delete result.wrappedJSObject;
delete result.ID;
delete result.innerID;
delete result.consoleID;
result.arguments = Array.map(aMessage.arguments || [], (aObj) => {
let dbgObj = this.makeDebuggeeValue(aObj, aUseObjectGlobal);
--- a/devtools/shared/webconsole/test/chrome.ini
+++ b/devtools/shared/webconsole/test/chrome.ini
@@ -1,26 +1,28 @@
[DEFAULT]
tags = devtools
skip-if = buildapp == 'b2g'
support-files =
common.js
data.json
data.json^headers^
+ helper_serviceworker.js
network_requests_iframe.html
sandboxed_iframe.html
console-test-worker.js
[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_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]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/helper_serviceworker.js
@@ -0,0 +1,2 @@
+
+console.log("Hello from serviceworker");
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_console_serviceworker.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the Console API 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 the Console API and Service Workers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let SERVICE_WORKER_URL = "https://example.com/chrome/devtools/shared/webconsole/test/helper_serviceworker.js";
+let FRAME_URL = "https://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html";
+
+let swClosed = new Promise(() => {});
+let expectedConsoleCalls = [
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ["Hello from serviceworker"],
+ }
+];
+let consoleCalls = [];
+
+let startTest = Task.async(function*() {
+ removeEventListener("load", startTest);
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["devtools.webconsole.filter.serviceworkers", true]
+ ]}, resolve);
+ });
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+});
+addEventListener("load", startTest);
+
+function onAttach(state, response) {
+ onConsoleAPICall = onConsoleAPICall.bind(null, state);
+ state.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+
+ info("Loading a ServiceWorker that will use console API");
+ swClosed = new Promise(resolve => {
+ let iframe = document.createElement("iframe");
+ iframe.onload = function() {
+ let win = iframe.contentWindow;
+ info("Registering the service worker");
+ win.navigator.serviceWorker.register(SERVICE_WORKER_URL).then(swr => {
+
+ info("Service worker registered. Unregistering");
+ swr.unregister().then(() => {
+ resolve();
+ });
+ }, error => {
+ info("Error registering service worker: " + error);
+ });
+ };
+ iframe.src = FRAME_URL;
+
+ document.body.appendChild(iframe);
+ });
+}
+
+function onConsoleAPICall(state, type, packet) {
+ info("received message level: " + packet.message.level);
+ is(packet.from, state.actor, "console API call actor");
+
+ consoleCalls.push(packet.message);
+ if (consoleCalls.length != expectedConsoleCalls.length) {
+ return;
+ }
+
+ state.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+
+ expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+ info("checking received console call #" + aIndex);
+ checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+ });
+
+ consoleCalls = [];
+
+ closeDebugger(state, function() {
+ swClosed.then(() => {
+ SimpleTest.finish();
+ });
+ });
+}
+</script>
+</body>
+</html>
--- a/devtools/shared/webconsole/utils.js
+++ b/devtools/shared/webconsole/utils.js
@@ -47,16 +47,27 @@ var WebConsoleUtils = {
{
let str = Cc["@mozilla.org/supports-string;1"].
createInstance(Ci.nsISupportsString);
str.data = aString;
return str;
},
/**
+ * Given a message, return one of CONSOLE_WORKER_IDS if it matches
+ * one of those.
+ *
+ * @return string
+ */
+ getWorkerType: function(message) {
+ let id = message ? message.innerID : null;
+ return CONSOLE_WORKER_IDS[CONSOLE_WORKER_IDS.indexOf(id)] || null;
+ },
+
+ /**
* Clone an object.
*
* @param object aObject
* The object you want cloned.
* @param boolean aRecursive
* Tells if you want to dig deeper into the object, to clone
* recursively.
* @param function [aFilter]
@@ -111,16 +122,39 @@ var WebConsoleUtils = {
let style = win.getComputedStyle(aFrom);
aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
},
/**
+ * Recursively gather a list of window locations given
+ * a top level window.
+ *
+ * @param nsIDOMWindow aWindow
+ * @return Array
+ * list of window locations as strings
+ */
+ getLocationsForFrames: function(aWindow)
+ {
+ let location = aWindow.location.toString();
+ let locations = [location];
+
+ if (aWindow.frames) {
+ for (let i = 0; i < aWindow.frames.length; i++) {
+ let frame = aWindow.frames[i];
+ locations = locations.concat(this.getLocationsForFrames(frame));
+ }
+ }
+
+ return locations;
+ },
+
+ /**
* Gets the ID of the inner window of this DOM window.
*
* @param nsIDOMWindow aWindow
* @return integer
* Inner ID for the given aWindow.
*/
getInnerWindowId: function WCU_getInnerWindowId(aWindow)
{
@@ -930,31 +964,64 @@ ConsoleAPIListener.prototype =
if (!this.owner) {
return;
}
// Here, wrappedJSObject is not a security wrapper but a property defined
// by the XPCOM component which allows us to unwrap the XPCOM interface and
// access the underlying JSObject.
let apiMessage = aMessage.wrappedJSObject;
- if (this.window && CONSOLE_WORKER_IDS.indexOf(apiMessage.innerID) == -1) {
- let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
- if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
- // Not the same window!
- return;
- }
- }
- if (this.consoleID && apiMessage.consoleID != this.consoleID) {
+
+ if (!this.isMessageRelevant(apiMessage)) {
return;
}
this.owner.onConsoleAPICall(apiMessage);
},
/**
+ * Given a message, return true if this window should show it and false
+ * if it should be ignored.
+ *
+ * @param message
+ * The message from the Storage Service
+ * @return bool
+ * Do we care about this message?
+ */
+ isMessageRelevant: function(message) {
+ let workerType = WebConsoleUtils.getWorkerType(message);
+
+ if (this.window && workerType === "ServiceWorker") {
+ // For messages from Service Workers, message.ID is the
+ // scope, which can be used to determine whether it's controlling
+ // a window.
+ let scope = message.ID;
+ let locations = WebConsoleUtils.getLocationsForFrames(this.window);
+
+ if (!locations.some(loc => loc.startsWith(scope))) {
+ return false;
+ }
+ }
+
+ if (this.window && !workerType) {
+ let msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
+ if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
+ // Not the same window!
+ return false;
+ }
+ }
+
+ if (this.consoleID && message.consoleID !== this.consoleID) {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
* Get the cached messages for the current inner window and its (i)frames.
*
* @param boolean [aIncludePrivate=false]
* Tells if you want to also retrieve messages coming from private
* windows. Defaults to false.
* @return array
* The array of cached messages.
*/
@@ -974,19 +1041,19 @@ ConsoleAPIListener.prototype =
messages = messages.concat(ConsoleAPIStorage.getEvents(id));
});
}
CONSOLE_WORKER_IDS.forEach((id) => {
messages = messages.concat(ConsoleAPIStorage.getEvents(id));
});
- if (this.consoleID) {
- messages = messages.filter((m) => m.consoleID == this.consoleID);
- }
+ messages = messages.filter(msg => {
+ return this.isMessageRelevant(msg);
+ });
if (aIncludePrivate) {
return messages;
}
return messages.filter((m) => !m.private);
},
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -493,26 +493,30 @@ private:
if (aOuterWindow) {
mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
} else {
ConsoleStackEntry frame;
if (mCallData->mTopStackFrame) {
frame = *mCallData->mTopStackFrame;
}
- nsString id;
+ nsString id = frame.mFilename;
+ nsString innerID;
if (mWorkerPrivate->IsSharedWorker()) {
- id = NS_LITERAL_STRING("SharedWorker");
+ innerID = NS_LITERAL_STRING("SharedWorker");
} else if (mWorkerPrivate->IsServiceWorker()) {
- id = NS_LITERAL_STRING("ServiceWorker");
+ innerID = NS_LITERAL_STRING("ServiceWorker");
+ // Use scope as ID so the webconsole can decide if the message should
+ // show up per tab
+ id.AssignWithConversion(mWorkerPrivate->WorkerName());
} else {
- id = NS_LITERAL_STRING("Worker");
+ innerID = NS_LITERAL_STRING("Worker");
}
- mCallData->SetIDs(frame.mFilename, id);
+ mCallData->SetIDs(id, innerID);
}
// Now we could have the correct window (if we are not window-less).
mClonedData.mParent = aInnerWindow;
ProcessCallData(aCx);
mCallData->CleanupJSObjects();