Bug 1756823 - Replace ConsoleAPIStorage observer calls with js array r=nchevobbe,webdriver-reviewers,geckoview-reviewers,agi,jdescottes

See the comment in the file explaining it. For a case of logging 100k numbers,
this dropped the time per number from 15 microseconds to 9 with the console
closed, and 55 microseconds to 38 with the console open. I think we could shave
off more with a native approach, but I don't know that it's worth it and it's
much more likely for that to introduce bugs.

Differential Revision: https://phabricator.services.mozilla.com/D143782
This commit is contained in:
Doug Thayer 2022-05-03 17:21:59 +00:00
Родитель e5f2d78d34
Коммит 04d1c8fd1e
46 изменённых файлов: 672 добавлений и 1069 удалений

Просмотреть файл

@ -25,7 +25,14 @@ async function checkMessages(expectedResult) {
}; };
Services.console.registerListener(errorListener); Services.console.registerListener(errorListener);
Services.obs.addObserver(errorListener, "console-api-log-event");
const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
ConsoleAPIStorage.addLogEventListener(
errorListener.observe,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
await setupPolicyEngineWithJson({ await setupPolicyEngineWithJson({
policies: {}, policies: {},

Просмотреть файл

@ -185,21 +185,24 @@ registerCleanupFunction(function() {
/** /**
* Watch console messages for failed propType definitions in React components. * Watch console messages for failed propType definitions in React components.
*/ */
const ConsoleObserver = { function onConsoleMessage(subject) {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), const message = subject.wrappedJSObject.arguments[0];
observe: function(subject) { if (message && /Failed propType/.test(message.toString())) {
const message = subject.wrappedJSObject.arguments[0]; ok(false, message);
}
}
if (message && /Failed propType/.test(message.toString())) { const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
ok(false, message); Ci.nsIConsoleAPIStorage
} );
},
};
Services.obs.addObserver(ConsoleObserver, "console-api-log-event"); ConsoleAPIStorage.addLogEventListener(
onConsoleMessage,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
registerCleanupFunction(() => { registerCleanupFunction(() => {
Services.obs.removeObserver(ConsoleObserver, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(onConsoleMessage);
}); });
Services.prefs.setBoolPref("devtools.inspector.three-pane-enabled", true); Services.prefs.setBoolPref("devtools.inspector.three-pane-enabled", true);

Просмотреть файл

@ -200,7 +200,7 @@ function getConsoleTableMessageItems(targetActor, result) {
* @param TargetActor targetActor * @param TargetActor targetActor
* The related target actor * The related target actor
* @param object message * @param object message
* The original message received from console-api-log-event. * The original message received from the console storage listener.
* @return object * @return object
* The object that can be sent to the remote client. * The object that can be sent to the remote client.
*/ */

Просмотреть файл

@ -2044,7 +2044,7 @@ const WebConsoleActor = ActorClassWithSpec(webconsoleSpec, {
* instance. * instance.
* *
* @param object message * @param object message
* The original message received from console-api-log-event. * The original message received from the console storage listener.
* @param boolean aUseObjectGlobal * @param boolean aUseObjectGlobal
* If |true| the object global is determined and added as a debuggee, * If |true| the object global is determined and added as a debuggee,
* otherwise |this.global| is used when makeDebuggeeValue() is invoked. * otherwise |this.global| is used when makeDebuggeeValue() is invoked.

Просмотреть файл

@ -5,6 +5,9 @@
"use strict"; "use strict";
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
@ -12,6 +15,12 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/E10SUtils.jsm" "resource://gre/modules/E10SUtils.jsm"
); );
XPCOMUtils.defineLazyGetter(this, "ConsoleAPIStorage", () => {
return Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
});
/* /*
* The message manager has an upper limit on message sizes that it can * The message manager has an upper limit on message sizes that it can
* reliably forward to the parent so we limit the size of console log event * reliably forward to the parent so we limit the size of console log event
@ -34,7 +43,11 @@ const MSG_MGR_CONSOLE_VAR_SIZE = 8;
const MSG_MGR_CONSOLE_INFO_MAX = 1024; const MSG_MGR_CONSOLE_INFO_MAX = 1024;
function ContentProcessForward() { function ContentProcessForward() {
Services.obs.addObserver(this, "console-api-log-event"); this.onConsoleAPILogEvent = this.onConsoleAPILogEvent.bind(this);
ConsoleAPIStorage.addLogEventListener(
this.onConsoleAPILogEvent,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
Services.obs.addObserver(this, "xpcom-shutdown"); Services.obs.addObserver(this, "xpcom-shutdown");
Services.cpmm.addMessageListener( Services.cpmm.addMessageListener(
"DevTools:StopForwardingContentProcessMessage", "DevTools:StopForwardingContentProcessMessage",
@ -53,81 +66,76 @@ ContentProcessForward.prototype = {
} }
}, },
observe(subject, topic, data) { onConsoleAPILogEvent(subject, data) {
switch (topic) { const consoleMsg = subject.wrappedJSObject;
case "console-api-log-event": {
const consoleMsg = subject.wrappedJSObject;
const msgData = { const msgData = {
...consoleMsg, ...consoleMsg,
arguments: [], arguments: [],
filename: consoleMsg.filename.substring(0, MSG_MGR_CONSOLE_INFO_MAX), filename: consoleMsg.filename.substring(0, MSG_MGR_CONSOLE_INFO_MAX),
functionName: functionName:
consoleMsg.functionName && consoleMsg.functionName &&
consoleMsg.functionName.substring(0, MSG_MGR_CONSOLE_INFO_MAX), consoleMsg.functionName.substring(0, MSG_MGR_CONSOLE_INFO_MAX),
// Prevents cyclic object error when using msgData in sendAsyncMessage // Prevents cyclic object error when using msgData in sendAsyncMessage
wrappedJSObject: null, wrappedJSObject: null,
}; };
// We can't send objects over the message manager, so we sanitize // We can't send objects over the message manager, so we sanitize
// them out, replacing those arguments with "<unavailable>". // them out, replacing those arguments with "<unavailable>".
const unavailString = "<unavailable>"; const unavailString = "<unavailable>";
const unavailStringLength = unavailString.length * 2; // 2-bytes per char const unavailStringLength = unavailString.length * 2; // 2-bytes per char
// When the sum of argument sizes reaches MSG_MGR_CONSOLE_MAX_SIZE, // When the sum of argument sizes reaches MSG_MGR_CONSOLE_MAX_SIZE,
// replace all arguments with "<truncated>". // replace all arguments with "<truncated>".
let totalArgLength = 0; let totalArgLength = 0;
// Walk through the arguments, checking the type and size. // Walk through the arguments, checking the type and size.
for (let arg of consoleMsg.arguments) { for (let arg of consoleMsg.arguments) {
if ( if (
(typeof arg == "object" || typeof arg == "function") && (typeof arg == "object" || typeof arg == "function") &&
arg !== null arg !== null
) { ) {
if ( if (Services.appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE) {
Services.appinfo.remoteType === E10SUtils.EXTENSION_REMOTE_TYPE // For OOP extensions: we want the developer to be able to see the
) { // logs in the Browser Console. When the Addon Toolbox will be more
// For OOP extensions: we want the developer to be able to see the // prominent we can revisit.
// logs in the Browser Console. When the Addon Toolbox will be more try {
// prominent we can revisit. // If the argument is clonable, then send it as-is. If
try { // cloning fails, fall back to the unavailable string.
// If the argument is clonable, then send it as-is. If arg = Cu.cloneInto(arg, {});
// cloning fails, fall back to the unavailable string. } catch (e) {
arg = Cu.cloneInto(arg, {}); arg = unavailString;
} catch (e) {
arg = unavailString;
}
} else {
arg = unavailString;
}
totalArgLength += unavailStringLength;
} else if (typeof arg == "string") {
totalArgLength += arg.length * 2; // 2-bytes per char
} else {
totalArgLength += MSG_MGR_CONSOLE_VAR_SIZE;
}
if (totalArgLength <= MSG_MGR_CONSOLE_MAX_SIZE) {
msgData.arguments.push(arg);
} else {
// arguments take up too much space
msgData.arguments = ["<truncated>"];
break;
} }
} else {
arg = unavailString;
} }
totalArgLength += unavailStringLength;
Services.cpmm.sendAsyncMessage("Console:Log", msgData); } else if (typeof arg == "string") {
break; totalArgLength += arg.length * 2; // 2-bytes per char
} else {
totalArgLength += MSG_MGR_CONSOLE_VAR_SIZE;
} }
case "xpcom-shutdown": if (totalArgLength <= MSG_MGR_CONSOLE_MAX_SIZE) {
this.uninit(); msgData.arguments.push(arg);
} else {
// arguments take up too much space
msgData.arguments = ["<truncated>"];
break; break;
}
}
Services.cpmm.sendAsyncMessage("Console:Log", msgData);
},
observe(subject, topic, data) {
if (topic == "xpcom-shutdown") {
this.uninit();
} }
}, },
uninit() { uninit() {
Services.obs.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.onConsoleAPILogEvent);
Services.obs.removeObserver(this, "xpcom-shutdown"); Services.obs.removeObserver(this, "xpcom-shutdown");
Services.cpmm.removeMessageListener( Services.cpmm.removeMessageListener(
"DevTools:StopForwardingContentProcessMessage", "DevTools:StopForwardingContentProcessMessage",

Просмотреть файл

@ -5,7 +5,6 @@
"use strict"; "use strict";
const { Cc, Ci } = require("chrome"); const { Cc, Ci } = require("chrome");
const Services = require("Services");
const ChromeUtils = require("ChromeUtils"); const ChromeUtils = require("ChromeUtils");
const { const {
CONSOLE_WORKER_IDS, CONSOLE_WORKER_IDS,
@ -59,7 +58,7 @@ class ConsoleAPIListener {
/** /**
* The function which is notified of window.console API calls. It is invoked with one * The function which is notified of window.console API calls. It is invoked with one
* argument: the console API call object that comes from the observer service. * argument: the console API call object that comes from the ConsoleAPIStorage service.
* *
* @type function * @type function
*/ */
@ -72,24 +71,34 @@ class ConsoleAPIListener {
addonId = null; addonId = null;
/** /**
* Initialize the window.console API observer. * Initialize the window.console API listener.
*/ */
init() { init() {
// Note that the observer is process-wide. We will filter the messages as const ConsoleAPIStorage = Cc[
// needed, see CAL_observe(). "@mozilla.org/consoleAPI-storage;1"
Services.obs.addObserver(this, "console-api-log-event"); ].getService(Ci.nsIConsoleAPIStorage);
// Note that the listener is process-wide. We will filter the messages as
// needed, see onConsoleAPILogEvent().
this.onConsoleAPILogEvent = this.onConsoleAPILogEvent.bind(this);
ConsoleAPIStorage.addLogEventListener(
this.onConsoleAPILogEvent,
// We create a principal here to get the privileged principal of this
// script. Note that this is importantly *NOT* the principal of the
// content we are observing, as that would not have access to the
// message object created in ConsoleAPIStorage.jsm's scope.
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
} }
/** /**
* The console API message observer. When messages are received from the * The console API message listener. When messages are received from the
* observer service we forward them to the remote Web Console instance. * ConsoleAPIStorage service we forward them to the remote Web Console instance.
* *
* @param object message * @param object message
* The message object receives from the observer service. * The message object receives from the ConsoleAPIStorage service.
* @param string topic
* The message topic received from the observer service.
*/ */
observe(message, topic) { onConsoleAPILogEvent(message) {
if (!this.handler) { if (!this.handler) {
return; return;
} }
@ -233,7 +242,10 @@ class ConsoleAPIListener {
* Destroy the console API listener. * Destroy the console API listener.
*/ */
destroy() { destroy() {
Services.obs.removeObserver(this, "console-api-log-event"); const ConsoleAPIStorage = Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(Ci.nsIConsoleAPIStorage);
ConsoleAPIStorage.removeLogEventListener(this.onConsoleAPILogEvent);
this.window = this.handler = null; this.window = this.handler = null;
} }
} }

Просмотреть файл

@ -291,17 +291,23 @@ const TESTS = {
// the text passed as argument // the text passed as argument
function onConsoleWarningLogged(warningMessage) { function onConsoleWarningLogged(warningMessage) {
return new Promise(resolve => { return new Promise(resolve => {
const observer = { const ConsoleAPIStorage = Cc[
observe(subject) { "@mozilla.org/consoleAPI-storage;1"
// This is the first argument passed to console.warn() ].getService(Ci.nsIConsoleAPIStorage);
const message = subject.wrappedJSObject.arguments[0];
if (message.includes(warningMessage)) { const observer = subject => {
Services.obs.removeObserver(observer, "console-api-log-event"); // This is the first argument passed to console.warn()
resolve(); const message = subject.wrappedJSObject.arguments[0];
} if (message.includes(warningMessage)) {
}, ConsoleAPIStorage.removeLogEventListener(observer);
resolve();
}
}; };
Services.obs.addObserver(observer, "console-api-log-event");
ConsoleAPIStorage.addLogEventListener(
observer,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
}); });
} }

Просмотреть файл

@ -1532,15 +1532,13 @@ void MainThreadConsoleData::ProcessCallData(
return; return;
} }
nsAutoString innerID, outerID; nsAutoString innerID;
MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown); MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
if (aData->mIDType == ConsoleCallData::eString) { if (aData->mIDType == ConsoleCallData::eString) {
outerID = aData->mOuterIDString;
innerID = aData->mInnerIDString; innerID = aData->mInnerIDString;
} else { } else {
MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber); MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
outerID.AppendInt(aData->mOuterIDNumber);
innerID.AppendInt(aData->mInnerIDNumber); innerID.AppendInt(aData->mInnerIDNumber);
} }
@ -1549,7 +1547,7 @@ void MainThreadConsoleData::ProcessCallData(
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed"); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "ClearEvents failed");
} }
if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) { if (NS_FAILED(mStorage->RecordEvent(innerID, eventValue))) {
NS_WARNING("Failed to record a console event."); NS_WARNING("Failed to record a console event.");
} }
} }

Просмотреть файл

@ -10,6 +10,12 @@ const STORAGE_MAX_EVENTS = 1000;
var _consoleStorage = new Map(); var _consoleStorage = new Map();
// NOTE: these listeners used to just be added as observers and notified via
// Services.obs.notifyObservers. However, that has enough overhead to be a
// problem for this. Using an explicit global array is much cheaper, and
// should be equivalent.
var _logEventListeners = [];
const CONSOLEAPISTORAGE_CID = Components.ID( const CONSOLEAPISTORAGE_CID = Components.ID(
"{96cf7855-dfa9-4c6d-8276-f9705b4890f2}" "{96cf7855-dfa9-4c6d-8276-f9705b4890f2}"
); );
@ -94,19 +100,59 @@ ConsoleAPIStorageService.prototype = {
}); });
}, },
/**
* Adds a listener to be notified of log events.
*
* @param jsval [aListener]
* A JS listener which will be notified with the message object when
* a log event occurs.
* @param nsIPrincipal [aPrincipal]
* The principal of the listener - used to determine if we need to
* clone the message before forwarding it.
*/
addLogEventListener: function CS_addLogEventListener(aListener, aPrincipal) {
// If our listener has a less-privileged principal than us, then they won't
// be able to access the log event object which was populated for our
// scope. Accordingly we need to clone it for these listeners.
//
// XXX: AFAICT these listeners which we need to clone messages for are all
// tests. Alternative solutions are welcome.
const clone = !aPrincipal.subsumes(
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
_logEventListeners.push({
callback: aListener,
clone,
});
},
/**
* Removes a listener added with `addLogEventListener`.
*
* @param jsval [aListener]
* A JS listener which was added with `addLogEventListener`.
*/
removeLogEventListener: function CS_removeLogEventListener(aListener) {
const index = _logEventListeners.findIndex(l => l.callback === aListener);
if (index != -1) {
_logEventListeners.splice(index, 1);
} else {
Cu.reportError(
"Attempted to remove a log event listener that does not exist."
);
}
},
/** /**
* Record an event associated with the given window ID. * Record an event associated with the given window ID.
* *
* @param string aId * @param string aId
* The ID of the inner window for which the event occurred or "jsm" for * The ID of the inner window for which the event occurred or "jsm" for
* messages logged from JavaScript modules.. * messages logged from JavaScript modules..
* @param string aOuterId
* This ID is used as 3rd parameters for the console-api-log-event
* notification.
* @param object aEvent * @param object aEvent
* A JavaScript object you want to store. * A JavaScript object you want to store.
*/ */
recordEvent: function CS_recordEvent(aId, aOuterId, aEvent) { recordEvent: function CS_recordEvent(aId, aEvent) {
if (!_consoleStorage.has(aId)) { if (!_consoleStorage.has(aId)) {
_consoleStorage.set(aId, []); _consoleStorage.set(aId, []);
} }
@ -120,8 +166,13 @@ ConsoleAPIStorageService.prototype = {
storage.shift(); storage.shift();
} }
Services.obs.notifyObservers(aEvent, "console-api-log-event", aOuterId); for (let { callback, clone } of _logEventListeners) {
Services.obs.notifyObservers(aEvent, "console-storage-cache-event", aId); if (clone) {
callback(Cu.cloneInto(aEvent, callback));
} else {
callback(aEvent);
}
}
}, },
/** /**

Просмотреть файл

@ -138,7 +138,7 @@ void ConsoleUtils::ReportForServiceWorkerScopeInternal(
return; return;
} }
storage->RecordEvent(u"ServiceWorker"_ns, aScope, eventValue); storage->RecordEvent(u"ServiceWorker"_ns, eventValue);
} }
JSObject* ConsoleUtils::GetOrCreateSandbox(JSContext* aCx) { JSObject* ConsoleUtils::GetOrCreateSandbox(JSContext* aCx) {

Просмотреть файл

@ -4,6 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl" #include "nsISupports.idl"
#include "nsIPrincipal.idl"
[scriptable, uuid(9e32a7b6-c4d1-4d9a-87b9-1ef6b75c27a9)] [scriptable, uuid(9e32a7b6-c4d1-4d9a-87b9-1ef6b75c27a9)]
interface nsIConsoleAPIStorage : nsISupports interface nsIConsoleAPIStorage : nsISupports
@ -21,19 +22,36 @@ interface nsIConsoleAPIStorage : nsISupports
*/ */
jsval getEvents([optional] in AString aId); jsval getEvents([optional] in AString aId);
/**
* Adds a listener to be notified of log events.
*
* @param jsval [aListener]
* A JS listener which will be notified with the message object when
* a log event occurs.
* @param nsIPrincipal [aPrincipal]
* The principal of the listener - used to determine if we need to
* clone the message before forwarding it.
*/
void addLogEventListener(in jsval aListener, in nsIPrincipal aPrincipal);
/**
* Removes a listener added with `addLogEventListener`.
*
* @param jsval [aListener]
* A JS listener which was added with `addLogEventListener`.
*/
void removeLogEventListener(in jsval aListener);
/** /**
* Record an event associated with the given window ID. * Record an event associated with the given window ID.
* *
* @param string aId * @param string aId
* The ID of the inner window for which the event occurred or "jsm" for * The ID of the inner window for which the event occurred or "jsm" for
* messages logged from JavaScript modules.. * messages logged from JavaScript modules..
* @param string aOuterId
* This ID is used as 3rd parameters for the console-api-log-event
* notification.
* @param object aEvent * @param object aEvent
* A JavaScript object you want to store. * A JavaScript object you want to store.
*/ */
void recordEvent(in AString aId, in AString aOuterId, in jsval aEvent); void recordEvent(in AString aId, in jsval aEvent);
/** /**
* Clear storage data for the given window. * Clear storage data for the given window.

Просмотреть файл

@ -3,6 +3,7 @@ skip-if = os == 'android'
support-files = support-files =
file_empty.html file_empty.html
console.jsm console.jsm
head.js
[test_console.xhtml] [test_console.xhtml]
[test_jsm.xhtml] [test_jsm.xhtml]

24
dom/console/tests/head.js Normal file
Просмотреть файл

@ -0,0 +1,24 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
// This is intended to just be a drop-in replacement for an old observer
// notification.
function addConsoleStorageListener(listener) {
listener.__handler = (message, id) => {
listener.observe(message, id);
};
ConsoleAPIStorage.addLogEventListener(
listener.__handler,
SpecialPowers.wrap(document).nodePrincipal
);
}
function removeConsoleStorageListener(listener) {
ConsoleAPIStorage.removeLogEventListener(listener.__handler);
}

Просмотреть файл

@ -1,6 +1,7 @@
[DEFAULT] [DEFAULT]
support-files = support-files =
file_empty.html file_empty.html
head.js
[test_bug659625.html] [test_bug659625.html]
[test_bug978522.html] [test_bug978522.html]

Просмотреть файл

@ -4,30 +4,28 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Test Console binding</title> <title>Test Console binding</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head> </head>
<body> <body>
<script type="application/javascript"> <script type="application/javascript">
function consoleListener() { function consoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
var order = 0; var order = 0;
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(obj) {
if (aTopic == "console-api-log-event") { ok(!obj.chromeContext, "Thils is not a chrome context");
var obj = aSubject.wrappedJSObject; if (order + 1 == parseInt(obj.arguments[0])) {
ok(!obj.chromeContext, "Thils is not a chrome context"); ok(true, "Message received: " + obj.arguments[0]);
if (order + 1 == parseInt(obj.arguments[0])) { order++;
ok(true, "Message received: " + obj.arguments[0]); }
order++;
}
if (order == 3) { if (order == 3) {
SpecialPowers.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
SimpleTest.finish(); SimpleTest.finish();
}
} }
}, },
}; };

Просмотреть файл

@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Test for count/countReset in console</title> <title>Test for count/countReset in console</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head> </head>
<body> <body>
@ -12,11 +13,11 @@
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
function ConsoleListener() { function ConsoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
ConsoleListener.prototype = { ConsoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
let obj = aSubject.wrappedJSObject; let obj = aSubject.wrappedJSObject;
if (obj.arguments[0] != "test") { if (obj.arguments[0] != "test") {
return; return;
@ -36,7 +37,7 @@ ConsoleListener.prototype = {
}, },
shutdown() { shutdown() {
SpecialPowers.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
}, },
waitFor(cb) { waitFor(cb) {

Просмотреть файл

@ -9,6 +9,7 @@
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script src="head.js"/>
<script type="application/javascript"> <script type="application/javascript">
<![CDATA[ <![CDATA[
@ -29,52 +30,50 @@ function promiseConsoleListenerCalled() {
let consoleListener = { let consoleListener = {
count: 0, count: 0,
observe: function(aSubject, aTopic, aData) { observe: function(aSubject) {
if (aTopic == "console-api-log-event") { var obj = aSubject.wrappedJSObject;
var obj = aSubject.wrappedJSObject; ok(obj.chromeContext, "JSM is always a chrome context");
ok(obj.chromeContext, "JSM is always a chrome context");
if (obj.innerID == JSM) { if (obj.innerID == JSM) {
is(obj.ID, "jsm", "ID and InnerID are correctly set."); is(obj.ID, "jsm", "ID and InnerID are correctly set.");
is(obj.arguments[0], "Hello world!", "Message matches"); is(obj.arguments[0], "Hello world!", "Message matches");
is(obj.consoleID, "", "No consoleID for console API"); is(obj.consoleID, "", "No consoleID for console API");
is(obj.prefix, "", "prefix is empty by default"); is(obj.prefix, "", "prefix is empty by default");
// We want to see 2 messages from this innerID, the first is generated // We want to see 2 messages from this innerID, the first is generated
// by console.log, the second one from createInstance().log(); // by console.log, the second one from createInstance().log();
++this.count; ++this.count;
} else if (obj.innerID == "CUSTOM INNER") { } else if (obj.innerID == "CUSTOM INNER") {
is(obj.ID, "jsm", "ID and InnerID are correctly set."); is(obj.ID, "jsm", "ID and InnerID are correctly set.");
is(obj.arguments[0], "Hello world!", "Message matches"); is(obj.arguments[0], "Hello world!", "Message matches");
is(obj.consoleID, "wow", "consoleID is set by consoleInstance"); is(obj.consoleID, "wow", "consoleID is set by consoleInstance");
is(obj.prefix, "_PREFIX_", "prefix is set by consoleInstance"); is(obj.prefix, "_PREFIX_", "prefix is set by consoleInstance");
// We expect to see 2 messages from this innerID. // We expect to see 2 messages from this innerID.
++this.count; ++this.count;
} else if (obj.innerID == "LEVEL") { } else if (obj.innerID == "LEVEL") {
// Nothing special... just we don't want to see 'invisible' messages. // Nothing special... just we don't want to see 'invisible' messages.
is(obj.ID, "jsm", "ID and InnerID are correctly set."); is(obj.ID, "jsm", "ID and InnerID are correctly set.");
is(obj.arguments[0], "Hello world!", "Message matches"); is(obj.arguments[0], "Hello world!", "Message matches");
is(obj.prefix, "", "prefix is empty by default"); is(obj.prefix, "", "prefix is empty by default");
// We expect to see 2 messages from this innerID. // We expect to see 2 messages from this innerID.
++this.count; ++this.count;
} else if (obj.innerID == "NO PREF") { } else if (obj.innerID == "NO PREF") {
// Nothing special... just we don't want to see 'invisible' messages. // Nothing special... just we don't want to see 'invisible' messages.
is(obj.ID, "jsm", "ID and InnerID are correctly set."); is(obj.ID, "jsm", "ID and InnerID are correctly set.");
is(obj.arguments[0], "Hello world!", "Message matches"); is(obj.arguments[0], "Hello world!", "Message matches");
is(obj.prefix, "", "prefix is empty by default"); is(obj.prefix, "", "prefix is empty by default");
// We expect to see 2 messages from this innerID. // We expect to see 2 messages from this innerID.
++this.count; ++this.count;
} }
if (this.count == 8) { if (this.count == 8) {
is(dumpCalled, 2, "Dump has been called!"); is(dumpCalled, 2, "Dump has been called!");
Services.obs.removeObserver(consoleListener, "console-api-log-event"); removeConsoleStorageListener(consoleListener);
resolve(); resolve();
}
} }
} }
} }
Services.obs.addObserver(consoleListener, "console-api-log-event"); addConsoleStorageListener(consoleListener);
}); });
} }

Просмотреть файл

@ -4,6 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Test for timeStart/timeLog/timeEnd in console</title> <title>Test for timeStart/timeLog/timeEnd in console</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head> </head>
<body> <body>
@ -15,11 +16,11 @@ var reduceTimePrecisionPrevPrefValue = SpecialPowers.getBoolPref("privacy.reduce
SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false); SpecialPowers.setBoolPref("privacy.reduceTimerPrecision", false);
function ConsoleListener() { function ConsoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
ConsoleListener.prototype = { ConsoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
let obj = aSubject.wrappedJSObject; let obj = aSubject.wrappedJSObject;
if (obj.arguments[0] != "test_bug1463614") { if (obj.arguments[0] != "test_bug1463614") {
return; return;
@ -34,7 +35,7 @@ ConsoleListener.prototype = {
}, },
shutdown() { shutdown() {
SpecialPowers.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
}, },
waitFor(cb) { waitFor(cb) {

Просмотреть файл

@ -0,0 +1,22 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
// This is intended to just be a drop-in replacement for an old observer
// notification.
function addConsoleStorageListener(listener) {
listener.__handler = (message, id) => {
listener.observe(message, id);
};
ConsoleAPIStorage.addLogEventListener(
listener.__handler,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
}
function removeConsoleStorageListener(listener) {
ConsoleAPIStorage.removeLogEventListener(listener.__handler);
}

Просмотреть файл

@ -8,17 +8,17 @@ add_task(async function() {
let p = new Promise(resolve => { let p = new Promise(resolve => {
function consoleListener() { function consoleListener() {
Services.obs.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
let obj = aSubject.wrappedJSObject; let obj = aSubject.wrappedJSObject;
Assert.ok(obj.arguments[0] === 42, "Message received!"); Assert.ok(obj.arguments[0] === 42, "Message received!");
Assert.ok(obj.ID === "jsm", "The ID is JSM"); Assert.ok(obj.ID === "jsm", "The ID is JSM");
Assert.ok(obj.innerID.endsWith("test_basic.js"), "The innerID matches"); Assert.ok(obj.innerID.endsWith("test_basic.js"), "The innerID matches");
Services.obs.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
resolve(); resolve();
}, },
}; };

Просмотреть файл

@ -40,11 +40,11 @@ add_task(async function() {
let t = 0; let t = 0;
function consoleListener() { function consoleListener() {
Services.obs.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
let test = tests[t++]; let test = tests[t++];
let obj = aSubject.wrappedJSObject; let obj = aSubject.wrappedJSObject;
@ -62,7 +62,7 @@ add_task(async function() {
} }
if (t === tests.length) { if (t === tests.length) {
Services.obs.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
resolve(); resolve();
} }
}, },

Просмотреть файл

@ -6,11 +6,11 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
add_task(async function() { add_task(async function() {
let p = new Promise(resolve => { let p = new Promise(resolve => {
function consoleListener() { function consoleListener() {
Services.obs.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
let obj = aSubject.wrappedJSObject; let obj = aSubject.wrappedJSObject;
Assert.ok(obj.arguments[0] === "Hello world!", "Message received!"); Assert.ok(obj.arguments[0] === "Hello world!", "Message received!");
Assert.ok(obj.ID === "scope", "The ID is the scope"); Assert.ok(obj.ID === "scope", "The ID is the scope");
@ -23,7 +23,7 @@ add_task(async function() {
Assert.ok(obj.columnNumber === 24, "The columnNumber matches"); Assert.ok(obj.columnNumber === 24, "The columnNumber matches");
Assert.ok(obj.level === "error", "The level is correct"); Assert.ok(obj.level === "error", "The level is correct");
Services.obs.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
resolve(); resolve();
}, },
}; };

Просмотреть файл

@ -1,5 +1,5 @@
[DEFAULT] [DEFAULT]
head = head = head.js
support-files = support-files =
[test_basic.js] [test_basic.js]

Просмотреть файл

@ -41,8 +41,6 @@ support-files =
support-files = support-files =
prevent_return_key.html prevent_return_key.html
[browser_ConsoleAPI_originAttributes.js] [browser_ConsoleAPI_originAttributes.js]
[browser_ConsoleAPITests.js]
skip-if = e10s
[browser_ConsoleStorageAPITests.js] [browser_ConsoleStorageAPITests.js]
[browser_ConsoleStoragePBTest_perwindowpb.js] [browser_ConsoleStoragePBTest_perwindowpb.js]
[browser_data_document_crossOriginIsolated.js] [browser_data_document_crossOriginIsolated.js]

Просмотреть файл

@ -1,638 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const TEST_URI =
"http://example.com/browser/dom/tests/browser/test-console-api.html";
add_task(async function() {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URI);
registerCleanupFunction(() => gBrowser.removeTab(tab));
let browser = gBrowser.selectedBrowser;
await consoleAPISanityTest(browser);
await observeConsoleTest(browser);
await startTraceTest(browser);
await startLocationTest(browser);
await startNativeCallbackTest(browser);
await startGroupTest(browser);
await startTimeTest(browser);
await startTimeEndTest(browser);
await startTimeStampTest(browser);
await startEmptyTimeStampTest(browser);
await startEmptyTimerTest(browser);
});
function spawnWithObserver(browser, observerFunc, func) {
// Build the observer generating function.
let source = [
"const TEST_URI = 'http://example.com/browser/dom/tests/browser/test-console-api.html';",
// Create a promise to be resolved when the text is complete. It is stored
// on content, such that it can be waited on by calling waitForResolve. This
// is done rather than returning it from this function such that the
// initialization can be yeilded on before yeilding on the conclusion of the
// test.
"content._promise = new Promise(_resolve => {",
// These are variables which are used by the test runner to communicate
// state to the observer.
" let gLevel, gArgs, gStyle;",
" let expect = function(level) {",
" gLevel = level;",
" gArgs = Array.prototype.slice.call(arguments, 1);",
" }",
// To ease the transition to the new format, content.window is avaliable as gWindow
// in the content.
" let gWindow = content.window;",
// This method is called rather than _resolve such that the observer is removed
// before exiting the test
" let resolve = () => {",
" Services.obs.removeObserver(ConsoleObserver, 'console-api-log-event');",
" _resolve();",
" };",
// This is the observer itself, it calls the passed-in function whenever
// it encounters an event
" let ConsoleObserver = {",
" QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),",
" observe: function(aSubject, aTopic, aData) {",
" try {",
" (" + observerFunc.toString() + ")(aSubject.wrappedJSObject);",
" } catch (ex) {",
" ok(false, 'Exception thrown in observe: ' + ex);",
" }",
" }",
" };",
" Services.obs.addObserver(ConsoleObserver, 'console-api-log-event', false);",
// Call the initialization function (if present)
func ? "(" + func.toString() + ")();" : "",
"});",
].join("\n");
return SpecialPowers.spawn(browser, [], new Function(source));
}
function waitForResolve(browser) {
return SpecialPowers.spawn(browser, [], function() {
return content._promise;
});
}
async function consoleAPISanityTest(browser) {
await SpecialPowers.spawn(browser, [], function() {
let win = XPCNativeWrapper.unwrap(content.window);
ok(win.console, "we have a console attached");
ok(win.console, "we have a console attached, 2nd attempt");
ok(win.console.log, "console.log is here");
ok(win.console.info, "console.info is here");
ok(win.console.warn, "console.warn is here");
ok(win.console.error, "console.error is here");
ok(win.console.exception, "console.exception is here");
ok(win.console.trace, "console.trace is here");
ok(win.console.dir, "console.dir is here");
ok(win.console.group, "console.group is here");
ok(win.console.groupCollapsed, "console.groupCollapsed is here");
ok(win.console.groupEnd, "console.groupEnd is here");
ok(win.console.time, "console.time is here");
ok(win.console.timeEnd, "console.timeEnd is here");
ok(win.console.timeStamp, "console.timeStamp is here");
ok(win.console.assert, "console.assert is here");
ok(win.console.count, "console.count is here");
});
}
// These globals are all defined in spawnWithObserver in a sub-process.
/* global gWindow, gArgs:true, gLevel:true, gStyle:true, expect, resolve */
function testConsoleData(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
ok(aMessageObject.arguments, "we have arguments");
switch (gLevel) {
case "trace": {
is(aMessageObject.arguments.length, 0, "arguments.length matches");
is(
aMessageObject.stacktrace.toSource(),
gArgs.toSource(),
"stack trace is correct"
);
break;
}
case "count": {
is(aMessageObject.counter.label, gArgs[0].label, "label matches");
is(aMessageObject.counter.count, gArgs[0].count, "count matches");
break;
}
default: {
is(
aMessageObject.arguments.length,
gArgs.length,
"arguments.length matches"
);
gArgs.forEach(function(a, i) {
// Waive Xray so that we don't get messed up by Xray ToString.
//
// It'd be nice to just use XPCNativeWrapper.unwrap here, but there are
// a number of dumb reasons we can't. See bug 868675.
var arg = aMessageObject.arguments[i];
if (Cu.isXrayWrapper(arg)) {
arg = arg.wrappedJSObject;
}
is(arg, a, "correct arg " + i);
});
if (gStyle) {
is(
aMessageObject.styles.length,
gStyle.length,
"styles.length matches"
);
is(aMessageObject.styles + "", gStyle + "", "styles match");
} else {
ok(
!aMessageObject.styles || aMessageObject.styles.length === 0,
"styles match"
);
}
}
}
}
async function observeConsoleTest(browser) {
await spawnWithObserver(browser, testConsoleData, function(opts) {
let win = XPCNativeWrapper.unwrap(content.window);
expect("log", "arg");
win.console.log("arg");
expect("info", "arg", "extra arg");
win.console.info("arg", "extra arg");
expect("warn", "Lesson 1: PI is approximately equal to 3");
win.console.warn(
"Lesson %d: %s is approximately equal to %1.0f",
1,
"PI",
3.14159
);
expect("warn", "Lesson 1: PI is approximately equal to 3.14");
win.console.warn(
"Lesson %d: %s is approximately equal to %1.2f",
1,
"PI",
3.14159
);
expect("warn", "Lesson 1: PI is approximately equal to 3.141590");
win.console.warn(
"Lesson %d: %s is approximately equal to %f",
1,
"PI",
3.14159
);
expect("warn", "Lesson 1: PI is approximately equal to 3.1415900");
win.console.warn(
"Lesson %d: %s is approximately equal to %0.7f",
1,
"PI",
3.14159
);
expect("log", "%d, %s, %l");
win.console.log("%d, %s, %l");
expect("log", "%a %b %g");
win.console.log("%a %b %g");
expect("log", "%a %b %g", "a", "b");
win.console.log("%a %b %g", "a", "b");
expect("log", "2, a, %l", 3);
win.console.log("%d, %s, %l", 2, "a", 3);
// Bug #692550 handle null and undefined.
expect("log", "null, undefined");
win.console.log("%s, %s", null, undefined);
// Bug #696288 handle object as first argument.
let obj = { a: 1 };
expect("log", obj, "a");
win.console.log(obj, "a");
expect("dir", win.toString());
win.console.dir(win);
expect("error", "arg");
win.console.error("arg");
expect("exception", "arg");
win.console.exception("arg");
expect("log", "foobar");
gStyle = ["color:red;foobar;;"];
win.console.log("%cfoobar", gStyle[0]);
let obj4 = { d: 4 };
expect("warn", "foobar", obj4, "test", "bazbazstr", "last");
gStyle = [null, null, null, "color:blue;", "color:red"];
win.console.warn(
"foobar%Otest%cbazbaz%s%clast",
obj4,
gStyle[3],
"str",
gStyle[4]
);
let obj3 = { c: 3 };
expect("info", "foobar", "bazbaz", obj3, "%comg", "color:yellow");
gStyle = [null, "color:pink;"];
win.console.info(
"foobar%cbazbaz",
gStyle[1],
obj3,
"%comg",
"color:yellow"
);
gStyle = null;
let obj2 = { b: 2 };
expect("log", "omg ", obj, " foo ", 4, obj2);
win.console.log("omg %o foo %o", obj, 4, obj2);
expect("assert", "message");
win.console.assert(false, "message");
expect("count", { label: "label a", count: 1 });
win.console.count("label a");
expect("count", { label: "label b", count: 1 });
win.console.count("label b");
expect("count", { label: "label a", count: 2 });
win.console.count("label a");
expect("count", { label: "label b", count: 2 });
win.console.count("label b");
dump("Resolving\n");
resolve();
});
dump("There\n");
}
function testTraceConsoleData(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
ok(aMessageObject.arguments, "we have arguments");
is(gLevel, "trace", "gLevel should be trace");
is(aMessageObject.arguments.length, 0, "arguments.length matches");
dump(aMessageObject.stacktrace.toSource() + "\n" + gArgs.toSource() + "\n");
is(
aMessageObject.stacktrace.toSource(),
gArgs.toSource(),
"stack trace is correct"
);
resolve();
}
async function startTraceTest(browser) {
dump("HERE\n");
await spawnWithObserver(browser, testTraceConsoleData, function(opts) {
dump("Observer attached\n");
gLevel = "trace";
gArgs = [
{
columnNumber: 9,
filename: TEST_URI,
functionName: "window.foobar585956c",
lineNumber: 6,
},
{
columnNumber: 16,
filename: TEST_URI,
functionName: "foobar585956b",
lineNumber: 11,
},
{
columnNumber: 16,
filename: TEST_URI,
functionName: "foobar585956a",
lineNumber: 15,
},
{
columnNumber: 1,
filename: TEST_URI,
functionName: "onclick",
lineNumber: 1,
},
];
});
BrowserTestUtils.synthesizeMouseAtCenter("#test-trace", {}, browser);
await waitForResolve(browser);
}
function testLocationData(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
ok(aMessageObject.arguments, "we have arguments");
is(aMessageObject.filename, gArgs[0].filename, "filename matches");
is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
is(
aMessageObject.functionName,
gArgs[0].functionName,
"functionName matches"
);
is(
aMessageObject.arguments.length,
gArgs[0].arguments.length,
"arguments.length matches"
);
gArgs[0].arguments.forEach(function(a, i) {
is(aMessageObject.arguments[i], a, "correct arg " + i);
});
resolve();
}
async function startLocationTest(browser) {
await spawnWithObserver(browser, testLocationData, function(opts) {
gLevel = "log";
gArgs = [
{
filename: TEST_URI,
functionName: "foobar646025",
arguments: ["omg", "o", "d"],
lineNumber: 19,
},
];
});
BrowserTestUtils.synthesizeMouseAtCenter("#test-location", {}, browser);
await waitForResolve(browser);
}
function testNativeCallback(aMessageObject) {
is(aMessageObject.level, "log", "expected level received");
is(aMessageObject.filename, "", "filename matches");
is(aMessageObject.lineNumber, 0, "lineNumber matches");
is(aMessageObject.functionName, "", "functionName matches");
resolve();
}
async function startNativeCallbackTest(browser) {
await spawnWithObserver(browser, testNativeCallback);
BrowserTestUtils.synthesizeMouseAtCenter("#test-nativeCallback", {}, browser);
await waitForResolve(browser);
}
function testConsoleGroup(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
ok(
aMessageObject.level == "group" ||
aMessageObject.level == "groupCollapsed" ||
aMessageObject.level == "groupEnd",
"expected level received"
);
is(aMessageObject.functionName, "testGroups", "functionName matches");
ok(
aMessageObject.lineNumber >= 46 && aMessageObject.lineNumber <= 50,
"lineNumber matches"
);
if (aMessageObject.level == "groupCollapsed") {
is(aMessageObject.groupName, "a group", "groupCollapsed groupName matches");
is(aMessageObject.arguments[0], "a", "groupCollapsed arguments[0] matches");
is(
aMessageObject.arguments[1],
"group",
"groupCollapsed arguments[0] matches"
);
} else if (aMessageObject.level == "group") {
is(aMessageObject.groupName, "b group", "group groupName matches");
is(aMessageObject.arguments[0], "b", "group arguments[0] matches");
is(aMessageObject.arguments[1], "group", "group arguments[1] matches");
} else if (aMessageObject.level == "groupEnd") {
is(aMessageObject.groupName, "b group", "groupEnd groupName matches");
}
if (aMessageObject.level == "groupEnd") {
resolve();
}
}
async function startGroupTest(browser) {
await spawnWithObserver(browser, testConsoleGroup);
BrowserTestUtils.synthesizeMouseAtCenter("#test-groups", {}, browser);
await waitForResolve(browser);
}
function testConsoleTime(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
is(aMessageObject.filename, gArgs[0].filename, "filename matches");
is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
is(
aMessageObject.functionName,
gArgs[0].functionName,
"functionName matches"
);
is(aMessageObject.timer.name, gArgs[0].timer.name, "timer.name matches");
gArgs[0].arguments.forEach(function(a, i) {
is(aMessageObject.arguments[i], a, "correct arg " + i);
});
resolve();
}
async function startTimeTest(browser) {
await spawnWithObserver(browser, testConsoleTime, function(opts) {
gLevel = "time";
gArgs = [
{
filename: TEST_URI,
lineNumber: 23,
functionName: "startTimer",
arguments: ["foo"],
timer: { name: "foo" },
},
];
});
BrowserTestUtils.synthesizeMouseAtCenter("#test-time", {}, browser);
await waitForResolve(browser);
}
function testConsoleTimeEnd(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
ok(aMessageObject.arguments, "we have arguments");
is(aMessageObject.filename, gArgs[0].filename, "filename matches");
is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
is(
aMessageObject.functionName,
gArgs[0].functionName,
"functionName matches"
);
is(
aMessageObject.arguments.length,
gArgs[0].arguments.length,
"arguments.length matches"
);
is(aMessageObject.timer.name, gArgs[0].timer.name, "timer name matches");
is(
typeof aMessageObject.timer.duration,
"number",
"timer duration is a number"
);
info("timer duration: " + aMessageObject.timer.duration);
ok(aMessageObject.timer.duration >= 0, "timer duration is positive");
gArgs[0].arguments.forEach(function(a, i) {
is(aMessageObject.arguments[i], a, "correct arg " + i);
});
resolve();
}
async function startTimeEndTest(browser) {
await spawnWithObserver(browser, testConsoleTimeEnd, function(opts) {
gLevel = "timeEnd";
gArgs = [
{
filename: TEST_URI,
lineNumber: 27,
functionName: "stopTimer",
arguments: ["foo"],
timer: { name: "foo" },
},
];
});
BrowserTestUtils.synthesizeMouseAtCenter("#test-timeEnd", {}, browser);
await waitForResolve(browser);
}
function testConsoleTimeStamp(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
is(aMessageObject.filename, gArgs[0].filename, "filename matches");
is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
is(
aMessageObject.functionName,
gArgs[0].functionName,
"functionName matches"
);
ok(aMessageObject.timeStamp > 0, "timeStamp is a positive value");
gArgs[0].arguments.forEach(function(a, i) {
is(aMessageObject.arguments[i], a, "correct arg " + i);
});
resolve();
}
async function startTimeStampTest(browser) {
await spawnWithObserver(browser, testConsoleTimeStamp, function() {
gLevel = "timeStamp";
gArgs = [
{
filename: TEST_URI,
lineNumber: 58,
functionName: "timeStamp",
arguments: ["!!!"],
},
];
});
BrowserTestUtils.synthesizeMouseAtCenter("#test-timeStamp", {}, browser);
await waitForResolve(browser);
}
function testEmptyConsoleTimeStamp(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
is(aMessageObject.level, gLevel, "expected level received");
is(aMessageObject.filename, gArgs[0].filename, "filename matches");
is(aMessageObject.lineNumber, gArgs[0].lineNumber, "lineNumber matches");
is(
aMessageObject.functionName,
gArgs[0].functionName,
"functionName matches"
);
ok(aMessageObject.timeStamp > 0, "timeStamp is a positive value");
is(aMessageObject.arguments.length, 0, "we don't have arguments");
resolve();
}
async function startEmptyTimeStampTest(browser) {
await spawnWithObserver(browser, testEmptyConsoleTimeStamp, function() {
gLevel = "timeStamp";
gArgs = [
{
filename: TEST_URI,
lineNumber: 58,
functionName: "timeStamp",
arguments: [],
},
];
});
BrowserTestUtils.synthesizeMouseAtCenter("#test-emptyTimeStamp", {}, browser);
await waitForResolve(browser);
}
function testEmptyTimer(aMessageObject) {
let messageWindow = Services.wm.getOuterWindowWithId(aMessageObject.ID);
is(messageWindow, gWindow, "found correct window by window ID");
ok(
aMessageObject.level == "time" || aMessageObject.level == "timeEnd",
"expected level received"
);
is(aMessageObject.arguments.length, 1, "we have the default argument");
is(aMessageObject.arguments[0], "default", "we have the default argument");
ok(aMessageObject.timer, "we have a timer");
is(aMessageObject.functionName, "namelessTimer", "functionName matches");
ok(
aMessageObject.lineNumber == 31 || aMessageObject.lineNumber == 32,
"lineNumber matches"
);
resolve();
}
async function startEmptyTimerTest(browser) {
await spawnWithObserver(browser, testEmptyTimer);
BrowserTestUtils.synthesizeMouseAtCenter("#test-namelessTimer", {}, browser);
await waitForResolve(browser);
}

Просмотреть файл

@ -12,50 +12,50 @@ const { WebExtensionPolicy } = Cu.getGlobalForObject(
const FAKE_ADDON_ID = "test-webext-addon@mozilla.org"; const FAKE_ADDON_ID = "test-webext-addon@mozilla.org";
const EXPECTED_CONSOLE_ID = `addon/${FAKE_ADDON_ID}`; const EXPECTED_CONSOLE_ID = `addon/${FAKE_ADDON_ID}`;
const EXPECTED_CONSOLE_MESSAGE_CONTENT = "fake-webext-addon-test-log-message"; const EXPECTED_CONSOLE_MESSAGE_CONTENT = "fake-webext-addon-test-log-message";
const ConsoleObserver = {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
const ConsoleObserver = {
init() { init() {
Services.obs.addObserver(this, "console-api-log-event"); ConsoleAPIStorage.addLogEventListener(
this.observe,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
}, },
uninit() { uninit() {
Services.obs.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.observe);
}, },
observe(aSubject, aTopic, aData) { observe(aSubject) {
if (aTopic == "console-api-log-event") { let consoleAPIMessage = aSubject.wrappedJSObject;
let consoleAPIMessage = aSubject.wrappedJSObject;
is( is(
consoleAPIMessage.arguments[0], consoleAPIMessage.arguments[0],
EXPECTED_CONSOLE_MESSAGE_CONTENT, EXPECTED_CONSOLE_MESSAGE_CONTENT,
"the consoleAPIMessage contains the expected message" "the consoleAPIMessage contains the expected message"
); );
is( is(
consoleAPIMessage.addonId, consoleAPIMessage.addonId,
FAKE_ADDON_ID, FAKE_ADDON_ID,
"the consoleAPImessage originAttributes contains the expected addonId" "the consoleAPImessage originAttributes contains the expected addonId"
); );
let cachedMessages = ConsoleAPIStorage.getEvents().filter(msg => { let cachedMessages = ConsoleAPIStorage.getEvents().filter(msg => {
return msg.addonId == FAKE_ADDON_ID; return msg.addonId == FAKE_ADDON_ID;
}); });
is( is(
cachedMessages.length, cachedMessages.length,
1, 1,
"found the expected cached console messages from the addon" "found the expected cached console messages from the addon"
); );
is( is(
cachedMessages[0] && cachedMessages[0].addonId, cachedMessages[0] && cachedMessages[0].addonId,
FAKE_ADDON_ID, FAKE_ADDON_ID,
"the cached message originAttributes contains the expected addonId" "the cached message originAttributes contains the expected addonId"
); );
finish(); finish();
}
}, },
}; };

Просмотреть файл

@ -35,23 +35,20 @@ add_task(async function() {
let observerPromise = new Promise(resolve => { let observerPromise = new Promise(resolve => {
let apiCallCount = 0; let apiCallCount = 0;
let ConsoleObserver = { function observe(aSubject) {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), apiCallCount++;
info(`Received ${apiCallCount} console log events`);
observe(aSubject, aTopic, aData) { if (apiCallCount == 4) {
if (aTopic == "console-storage-cache-event") { ConsoleAPIStorage.removeLogEventListener(observe);
apiCallCount++; resolve();
info(`Received ${apiCallCount} "console-storage-cache-event"`); }
if (apiCallCount == 4) { }
Services.obs.removeObserver(this, "console-storage-cache-event");
resolve();
}
}
},
};
info("Setting up observer"); info("Setting up observer");
Services.obs.addObserver(ConsoleObserver, "console-storage-cache-event"); ConsoleAPIStorage.addLogEventListener(
observe,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
}); });
info("Emit a few console API logs"); info("Emit a few console API logs");
@ -60,7 +57,7 @@ add_task(async function() {
content.console.warn("this", "is", "a", "warn", "message"); content.console.warn("this", "is", "a", "warn", "message");
content.console.error("this", "is", "a", "error", "message"); content.console.error("this", "is", "a", "error", "message");
info("Wait for the corresponding console-storage-cache-event"); info("Wait for the corresponding log event");
await observerPromise; await observerPromise;
const innerWindowId = content.windowGlobalChild.innerWindowId; const innerWindowId = content.windowGlobalChild.innerWindowId;

Просмотреть файл

@ -9,7 +9,6 @@ function test() {
let beforeEvents; let beforeEvents;
let afterEvents; let afterEvents;
let storageShouldOccur; let storageShouldOccur;
let consoleObserver;
let testURI = let testURI =
"http://example.com/browser/dom/tests/browser/test-console-api.html"; "http://example.com/browser/dom/tests/browser/test-console-api.html";
let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService( let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
@ -34,30 +33,23 @@ function test() {
function doTest(aIsPrivateMode, aWindow, aCallback) { function doTest(aIsPrivateMode, aWindow, aCallback) {
BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then( BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(
() => { () => {
consoleObserver = { function observe(aSubject) {
observe(aSubject, aTopic, aData) { afterEvents = ConsoleAPIStorage.getEvents(innerID);
if (aTopic == "console-api-log-event") { is(
afterEvents = ConsoleAPIStorage.getEvents(innerID); beforeEvents.length == afterEvents.length - 1,
is( storageShouldOccur,
beforeEvents.length == afterEvents.length - 1, "storage should" + (storageShouldOccur ? "" : " not") + " occur"
storageShouldOccur, );
"storage should" + (storageShouldOccur ? "" : " not") + " occur"
);
executeSoon(function() { executeSoon(function() {
Services.obs.removeObserver( ConsoleAPIStorage.removeLogEventListener(observe);
consoleObserver, aCallback();
"console-api-log-event" });
); }
aCallback();
});
}
},
};
aWindow.Services.obs.addObserver( ConsoleAPIStorage.addLogEventListener(
consoleObserver, observe,
"console-api-log-event" aWindow.document.nodePrincipal
); );
aWindow.nativeConsole.log( aWindow.nativeConsole.log(
"foo bar baz (private: " + aIsPrivateMode + ")" "foo bar baz (private: " + aIsPrivateMode + ")"

Просмотреть файл

@ -9,26 +9,29 @@ add_task(async function() {
await BrowserTestUtils.withNewTab(TEST_URI, async aBrowser => { await BrowserTestUtils.withNewTab(TEST_URI, async aBrowser => {
let duration = await SpecialPowers.spawn(aBrowser, [], function(opts) { let duration = await SpecialPowers.spawn(aBrowser, [], function(opts) {
const ConsoleAPIStorage = Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(Ci.nsIConsoleAPIStorage);
return new Promise(resolve => { return new Promise(resolve => {
let ConsoleObserver = { function observe(aSubject) {
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), var obj = aSubject.wrappedJSObject;
if (
obj.arguments.length != 1 ||
obj.arguments[0] != "bug1004814" ||
obj.level != "timeEnd"
) {
return;
}
observe(aSubject, aTopic, aData) { ConsoleAPIStorage.removeLogEventListener(observe);
var obj = aSubject.wrappedJSObject; resolve(obj.timer.duration);
if ( }
obj.arguments.length != 1 ||
obj.arguments[0] != "bug1004814" ||
obj.level != "timeEnd"
) {
return;
}
Services.obs.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.addLogEventListener(
resolve(obj.timer.duration); observe,
}, Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
}; );
Services.obs.addObserver(ConsoleObserver, "console-api-log-event");
var w = new content.Worker("worker_bug1004814.js"); var w = new content.Worker("worker_bug1004814.js");
w.postMessage(true); w.postMessage(true);

Просмотреть файл

@ -287,21 +287,42 @@ async function test_codeExecution(compartment) {
}); });
} }
// This is intended to just be a drop-in replacement for an old observer
// notification.
function addConsoleStorageListener(listener) {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
listener.__handler = (message, id) => {
listener.observe(message, id);
};
ConsoleAPIStorage.addLogEventListener(
listener.__handler,
SpecialPowers.wrap(document).nodePrincipal
);
}
function removeConsoleStorageListener(listener) {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
ConsoleAPIStorage.removeLogEventListener(listener.__handler);
}
async function test_codeExecution_continue(r, that) { async function test_codeExecution_continue(r, that) {
function consoleListener() { function consoleListener() {
that.SpecialPowers.addObserver(this, "console-api-log-event"); addConsoleStorageListener(this);
} }
var promise = new Promise(resolve => { var promise = new Promise(resolve => {
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
that.ok(true, "Something has been received"); that.ok(true, "Something has been received");
that.is(aTopic, "console-api-log-event");
var obj = aSubject.wrappedJSObject; var obj = aSubject.wrappedJSObject;
if (obj.arguments[0] && obj.arguments[0] === "pull called") { if (obj.arguments[0] && obj.arguments[0] === "pull called") {
that.ok(true, "Message received!"); that.ok(true, "Message received!");
that.SpecialPowers.removeObserver(this, "console-api-log-event"); removeConsoleStorageListener(this);
resolve(); resolve();
} }
}, },

Просмотреть файл

@ -10,14 +10,37 @@ add_task(async function test() {
await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab)); await BrowserTestUtils.browserLoaded(gBrowser.getBrowserForTab(tab));
let promise = new Promise(resolve => { let promise = new Promise(resolve => {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
Services.obs.addObserver(this, "console-api-log-event"); this.onConsoleLogEvent = this.onConsoleLogEvent.bind(this);
ConsoleAPIStorage.addLogEventListener(
this.onConsoleLogEvent,
SpecialPowers.wrap(document).nodePrincipal
);
Services.obs.addObserver(this, "console-api-profiler"); Services.obs.addObserver(this, "console-api-profiler");
} }
var order = 0; var order = 0;
consoleListener.prototype = { consoleListener.prototype = {
observe: (aSubject, aTopic, aData) => { onConsoleLogEvent(aSubject) {
var obj = aSubject.wrappedJSObject;
is(
obj.arguments[0],
"Hello world from a SharedWorker!",
"A message from a SharedWorker \\o/"
);
is(obj.ID, "sharedWorker_console.js", "The ID is SharedWorker");
is(obj.innerID, "SharedWorker", "The ID is SharedWorker");
is(order++, 1, "Then a log message.");
ConsoleAPIStorage.removeLogEventListener(this.onConsoleLogEvent);
resolve();
},
observe: (aSubject, aTopic) => {
ok(true, "Something has been received"); ok(true, "Something has been received");
if (aTopic == "console-api-profiler") { if (aTopic == "console-api-profiler") {
@ -30,23 +53,6 @@ add_task(async function test() {
is(order++, 0, "First a profiler message."); is(order++, 0, "First a profiler message.");
Services.obs.removeObserver(cl, "console-api-profiler"); Services.obs.removeObserver(cl, "console-api-profiler");
return;
}
if (aTopic == "console-api-log-event") {
var obj = aSubject.wrappedJSObject;
is(
obj.arguments[0],
"Hello world from a SharedWorker!",
"A message from a SharedWorker \\o/"
);
is(obj.ID, "sharedWorker_console.js", "The ID is SharedWorker");
is(obj.innerID, "SharedWorker", "The ID is SharedWorker");
is(order++, 1, "Then a log message.");
Services.obs.removeObserver(cl, "console-api-log-event");
resolve();
return;
} }
}, },
}; };

Просмотреть файл

@ -22,20 +22,23 @@
consoleMessagesReceived = 0; consoleMessagesReceived = 0;
function test() { function test() {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
Services.obs.addObserver(this, "console-api-log-event"); this.observe = this.observe.bind(this);
ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe: function(aSubject, aTopic, aData) { observe: function(aSubject) {
if (aTopic == "console-api-log-event") { var obj = aSubject.wrappedJSObject;
var obj = aSubject.wrappedJSObject; if (obj.arguments[0] == "Hello from the debugger script!" &&
if (obj.arguments[0] == "Hello from the debugger script!" && !consoleMessagesReceived) {
!consoleMessagesReceived) { consoleMessagesReceived++;
consoleMessagesReceived++; ok(true, "Something has been received");
ok(true, "Something has been received"); ConsoleAPIStorage.removeLogEventListener(this.observe);
Services.obs.removeObserver(this, "console-api-log-event");
}
} }
} }
} }

Просмотреть файл

@ -12,20 +12,23 @@
</head> </head>
<body> <body>
<script type="text/javascript"> <script type="text/javascript">
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); this.observe = this.observe.bind(this);
ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
} }
var order = 0; var order = 0;
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
ok(true, "Something has been received"); ok(true, "Something has been received");
is(aTopic, "console-api-log-event");
var obj = aSubject.wrappedJSObject; var obj = aSubject.wrappedJSObject;
if (obj.arguments[0] && obj.arguments[0].msg === 'consoleAndBlobs') { if (obj.arguments[0] && obj.arguments[0].msg === 'consoleAndBlobs') {
SpecialPowers.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.observe);
is(obj.arguments[0].blob.size, 3, "The size is correct"); is(obj.arguments[0].blob.size, 3, "The size is correct");
is(obj.arguments[0].blob.type, 'foo/bar', "The type is correct"); is(obj.arguments[0].blob.type, 'foo/bar', "The type is correct");
SimpleTest.finish(); SimpleTest.finish();

Просмотреть файл

@ -11,21 +11,24 @@
<script type="application/javascript"> <script type="application/javascript">
function configureTest() { function configureTest() {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); this.observe = this.observe.bind(this);
ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
if (aTopic == "console-api-log-event") { var obj = aSubject.wrappedJSObject;
var obj = aSubject.wrappedJSObject; if (obj.arguments[0] == "So far so good") {
if (obj.arguments[0] == "So far so good") { ok(true, "Message received \\o/");
ok(true, "Message received \\o/");
SpecialPowers.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.observe);
SimpleTest.finish(); SimpleTest.finish();
return; return;
}
} }
} }
} }

Просмотреть файл

@ -31,26 +31,29 @@ function configureTest() {
var expected_errors_i = 0; var expected_errors_i = 0;
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); this.observe = this.observe.bind(this);
ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
if (aTopic == "console-api-log-event") { var obj = aSubject.wrappedJSObject;
var obj = aSubject.wrappedJSObject; if (obj.arguments[0] == expected_errors[expected_errors_i]) {
if (obj.arguments[0] == expected_errors[expected_errors_i]) { ok(true, "Expected error received: " + obj.arguments[0]);
ok(true, "Expected error received: " + obj.arguments[0]); expected_errors_i++;
expected_errors_i++; }
}
if (expected_errors_i == expected_errors.length) { if (expected_errors_i == expected_errors.length) {
// All errors have been received, this test has been completed // All errors have been received, this test has been completed
// succesfully! // succesfully!
SpecialPowers.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.observe);
SimpleTest.finish(); SimpleTest.finish();
return; return;
}
} }
} }
} }

Просмотреть файл

@ -12,23 +12,26 @@
const WORKLET_SCRIPT = "worklet_console.js"; const WORKLET_SCRIPT = "worklet_console.js";
function configureTest() { function configureTest() {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); this.observe = this.observe.bind(this);
ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
if (aTopic == "console-api-log-event") { var obj = aSubject.wrappedJSObject;
var obj = aSubject.wrappedJSObject; if (obj.arguments[0] == "Hello world from a worklet") {
if (obj.arguments[0] == "Hello world from a worklet") { ok(true, "Message received \\o/");
ok(true, "Message received \\o/"); is(obj.filename,
is(obj.filename, new URL(WORKLET_SCRIPT, document.baseURI).toString());
new URL(WORKLET_SCRIPT, document.baseURI).toString());
SpecialPowers.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.observe);
SimpleTest.finish(); SimpleTest.finish();
return; return;
}
} }
} }
} }

Просмотреть файл

@ -11,21 +11,24 @@
<script type="application/javascript"> <script type="application/javascript">
function configureTest() { function configureTest() {
const ConsoleAPIStorage = SpecialPowers.Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(SpecialPowers.Ci.nsIConsoleAPIStorage);
function consoleListener() { function consoleListener() {
SpecialPowers.addObserver(this, "console-api-log-event"); this.observe = this.observe.bind(this);
ConsoleAPIStorage.addLogEventListener(this.observe, SpecialPowers.wrap(document).nodePrincipal);
} }
consoleListener.prototype = { consoleListener.prototype = {
observe(aSubject, aTopic, aData) { observe(aSubject) {
if (aTopic == "console-api-log-event") { var obj = aSubject.wrappedJSObject;
var obj = aSubject.wrappedJSObject; if (obj.arguments[0] == "So far so good") {
if (obj.arguments[0] == "So far so good") { ok(true, "Message received \\o/");
ok(true, "Message received \\o/");
SpecialPowers.removeObserver(this, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(this.observe);
SimpleTest.finish(); SimpleTest.finish();
return; return;
}
} }
} }
} }

Просмотреть файл

@ -18,8 +18,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
const { debug, warn } = GeckoViewUtils.initLogging("Console"); const { debug, warn } = GeckoViewUtils.initLogging("Console");
const LOG_EVENT_TOPIC = "console-api-log-event";
var GeckoViewConsole = { var GeckoViewConsole = {
_isEnabled: false, _isEnabled: false,
@ -34,21 +32,24 @@ var GeckoViewConsole = {
} }
this._isEnabled = !!aVal; this._isEnabled = !!aVal;
const ConsoleAPIStorage = Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(Ci.nsIConsoleAPIStorage);
if (this._isEnabled) { if (this._isEnabled) {
Services.obs.addObserver(this, LOG_EVENT_TOPIC); this._consoleMessageListener = this._handleConsoleMessage.bind(this);
} else { ConsoleAPIStorage.addLogEventListener(
Services.obs.removeObserver(this, LOG_EVENT_TOPIC); this._consoleMessageListener,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
} else if (this._consoleMessageListener) {
ConsoleAPIStorage.removeLogEventListener(this._consoleMessageListener);
delete this._consoleMessageListener;
} }
}, },
observe(aSubject, aTopic, aData) { observe(aSubject, aTopic, aData) {
switch (aTopic) { if (aTopic == "nsPref:changed") {
case "nsPref:changed": this.enabled = Services.prefs.getBoolPref(aData, false);
this.enabled = Services.prefs.getBoolPref(aData, false);
break;
case LOG_EVENT_TOPIC:
this._handleConsoleMessage(aSubject);
break;
} }
}, },

Просмотреть файл

@ -21,11 +21,15 @@ XPCOMUtils.defineLazyModuleGetters(this, {
"chrome://remote/content/cdp/domains/content/runtime/ExecutionContext.jsm", "chrome://remote/content/cdp/domains/content/runtime/ExecutionContext.jsm",
}); });
XPCOMUtils.defineLazyGetter(this, "ConsoleAPIStorage", () => {
return Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
});
// Import the `Debugger` constructor in the current scope // Import the `Debugger` constructor in the current scope
addDebuggerToGlobal(Cu.getGlobalForObject(this)); addDebuggerToGlobal(Cu.getGlobalForObject(this));
const OBSERVER_CONSOLE_API = "console-api-log-event";
const CONSOLE_API_LEVEL_MAP = { const CONSOLE_API_LEVEL_MAP = {
warn: "warning", warn: "warning",
}; };
@ -97,7 +101,11 @@ class Runtime extends ContentProcessDomain {
this.enabled = true; this.enabled = true;
Services.console.registerListener(this); Services.console.registerListener(this);
Services.obs.addObserver(this, OBSERVER_CONSOLE_API); this.onConsoleLogEvent = this.onConsoleLogEvent.bind(this);
ConsoleAPIStorage.addLogEventListener(
this.onConsoleLogEvent,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
// Spin the event loop in order to send the `executionContextCreated` event right // Spin the event loop in order to send the `executionContextCreated` event right
// after we replied to `enable` request. // after we replied to `enable` request.
@ -116,7 +124,7 @@ class Runtime extends ContentProcessDomain {
this.enabled = false; this.enabled = false;
Services.console.unregisterListener(this); Services.console.unregisterListener(this);
Services.obs.removeObserver(this, OBSERVER_CONSOLE_API); ConsoleAPIStorage.removeLogEventListener(this.onConsoleLogEvent);
} }
} }
@ -570,6 +578,11 @@ class Runtime extends ContentProcessDomain {
} }
} }
onConsoleLogEvent(message) {
let entry = fromConsoleAPI(message);
this._emitConsoleAPICalled(entry);
}
// nsIObserver // nsIObserver
/** /**
@ -581,14 +594,8 @@ class Runtime extends ContentProcessDomain {
* Console message. * Console message.
*/ */
observe(subject, topic, data) { observe(subject, topic, data) {
let entry; if (subject instanceof Ci.nsIScriptError && subject.hasException) {
let entry = fromScriptError(subject);
if (topic == OBSERVER_CONSOLE_API) {
const message = subject.wrappedJSObject;
entry = fromConsoleAPI(message);
this._emitConsoleAPICalled(entry);
} else if (subject instanceof Ci.nsIScriptError && subject.hasException) {
entry = fromScriptError(subject);
this._emitExceptionThrown(entry); this._emitExceptionThrown(entry);
} }
} }

Просмотреть файл

@ -12,7 +12,12 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
EventEmitter: "resource://gre/modules/EventEmitter.jsm", EventEmitter: "resource://gre/modules/EventEmitter.jsm",
Services: "resource://gre/modules/Services.jsm", });
XPCOMUtils.defineLazyGetter(this, "ConsoleAPIStorage", () => {
return Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
}); });
/** /**
@ -68,12 +73,12 @@ class ConsoleAPIListener {
return; return;
} }
Services.obs.addObserver( ConsoleAPIStorage.addLogEventListener(
this.#onConsoleAPIMessage, this.#onConsoleAPIMessage,
"console-api-log-event" Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
); );
// Emit cached messages after registering the observer, to make sure we // Emit cached messages after registering the listener, to make sure we
// don't miss any message. // don't miss any message.
this.#emitCachedMessages(); this.#emitCachedMessages();
@ -85,18 +90,11 @@ class ConsoleAPIListener {
return; return;
} }
Services.obs.removeObserver( ConsoleAPIStorage.removeLogEventListener(this.#onConsoleAPIMessage);
this.#onConsoleAPIMessage,
"console-api-log-event"
);
this.#listening = false; this.#listening = false;
} }
#emitCachedMessages() { #emitCachedMessages() {
const ConsoleAPIStorage = Cc[
"@mozilla.org/consoleAPI-storage;1"
].getService(Ci.nsIConsoleAPIStorage);
const cachedMessages = ConsoleAPIStorage.getEvents(this.#innerWindowId); const cachedMessages = ConsoleAPIStorage.getEvents(this.#innerWindowId);
for (const message of cachedMessages) { for (const message of cachedMessages) {
this.#onConsoleAPIMessage(message); this.#onConsoleAPIMessage(message);

Просмотреть файл

@ -24,6 +24,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { clearTimeout, setTimeout } = ChromeUtils.import( const { clearTimeout, setTimeout } = ChromeUtils.import(
"resource://gre/modules/Timer.jsm" "resource://gre/modules/Timer.jsm"
); );
const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
var TestUtils = { var TestUtils = {
executeSoon(callbackFn) { executeSoon(callbackFn) {
@ -34,6 +37,64 @@ var TestUtils = {
return new Promise(resolve => this.executeSoon(resolve)); return new Promise(resolve => this.executeSoon(resolve));
}, },
/**
* Waits for a console message matching the specified check function to be
* observed.
*
* @param {function} checkFn [optional]
* Called with the message as its argument, should return true if the
* notification is the expected one, or false if it should be ignored
* and listening should continue.
*
* @note Because this function is intended for testing, any error in checkFn
* will cause the returned promise to be rejected instead of waiting for
* the next notification, since this is probably a bug in the test.
*
* @return {Promise}
* @resolves The message from the observed notification.
*/
consoleMessageObserved(checkFn) {
return new Promise((resolve, reject) => {
let removed = false;
function observe(message) {
try {
if (checkFn && !checkFn(message)) {
return;
}
ConsoleAPIStorage.removeLogEventListener(observe);
// checkFn could reference objects that need to be destroyed before
// the end of the test, so avoid keeping a reference to it after the
// promise resolves.
checkFn = null;
removed = true;
resolve(message);
} catch (ex) {
ConsoleAPIStorage.removeLogEventListener(observe);
checkFn = null;
removed = true;
reject(ex);
}
}
ConsoleAPIStorage.addLogEventListener(
observe,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
TestUtils.promiseTestFinished?.then(() => {
if (removed) {
return;
}
ConsoleAPIStorage.removeLogEventListener(observe);
let text =
"Console message observer not removed before the end of test";
reject(text);
});
});
},
/** /**
* Waits for the specified topic to be observed. * Waits for the specified topic to be observed.
* *

Просмотреть файл

@ -137,17 +137,6 @@ SPConsoleListener.prototype = {
m.innerWindowID = msg.innerWindowID; m.innerWindowID = msg.innerWindowID;
m.isScriptError = true; m.isScriptError = true;
m.isWarning = (msg.flags & Ci.nsIScriptError.warningFlag) === 1; m.isWarning = (msg.flags & Ci.nsIScriptError.warningFlag) === 1;
} else if (topic === "console-api-log-event") {
// This is a dom/console event.
let unwrapped = msg.wrappedJSObject;
m.errorMessage = unwrapped.arguments[0];
m.sourceName = unwrapped.filename;
m.lineNumber = unwrapped.lineNumber;
m.columnNumber = unwrapped.columnNumber;
m.windowID = unwrapped.ID;
m.innerWindowID = unwrapped.innerID;
m.isConsoleEvent = true;
m.isWarning = unwrapped.level === "warning";
} }
Object.freeze(m); Object.freeze(m);
@ -159,7 +148,6 @@ SPConsoleListener.prototype = {
}); });
if (!m.isScriptError && !m.isConsoleEvent && m.message === "SENTINEL") { if (!m.isScriptError && !m.isConsoleEvent && m.message === "SENTINEL") {
Services.obs.removeObserver(this, "console-api-log-event");
Services.console.unregisterListener(this); Services.console.unregisterListener(this);
} }
}, },
@ -1197,9 +1185,6 @@ class SpecialPowersChild extends JSWindowActorChild {
registerConsoleListener(callback) { registerConsoleListener(callback) {
let listener = new SPConsoleListener(callback, this.contentWindow); let listener = new SPConsoleListener(callback, this.contentWindow);
Services.console.registerListener(listener); Services.console.registerListener(listener);
// listen for dom/console events as well
Services.obs.addObserver(listener, "console-api-log-event");
} }
postConsoleSentinel() { postConsoleSentinel() {
Services.console.logStringMessage("SENTINEL"); Services.console.logStringMessage("SENTINEL");

Просмотреть файл

@ -411,21 +411,25 @@ let consoleAllowList = [
'property "profileDir" is non-configurable and can\'t be deleted', 'property "profileDir" is non-configurable and can\'t be deleted',
]; ];
let consoleListener = { function observe(subject) {
observe(subject, topic, data) { let msg = subject.wrappedJSObject;
let msg = subject.wrappedJSObject; let messageContents = msg.arguments[0]?.message || msg.arguments[0];
let messageContents = msg.arguments[0]?.message || msg.arguments[0]; if (
if ( msg.level == "error" &&
msg.level == "error" && !consoleAllowList.some(e => messageContents.includes(e))
!consoleAllowList.some(e => messageContents.includes(e)) ) {
) { Assert.ok(false, "Unexpected console message: " + messageContents);
Assert.ok(false, "Unexpected console message: " + messageContents); }
} }
},
};
Services.obs.addObserver(consoleListener, "console-api-log-event"); const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
Ci.nsIConsoleAPIStorage
);
ConsoleAPIStorage.addLogEventListener(
observe,
Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
);
registerCleanupFunction(async () => { registerCleanupFunction(async () => {
Services.obs.removeObserver(consoleListener, "console-api-log-event"); ConsoleAPIStorage.removeLogEventListener(observe);
}); });

Просмотреть файл

@ -24,7 +24,7 @@ add_task(async function test_installedresourceicon() {
}); });
add_task(async function test_installedhttpplace() { add_task(async function test_installedhttpplace() {
let observed = TestUtils.topicObserved("console-api-log-event", msg => { let observed = TestUtils.consoleMessageObserved(msg => {
return msg.wrappedJSObject.arguments[0].includes( return msg.wrappedJSObject.arguments[0].includes(
"Content type does not match expected" "Content type does not match expected"
); );

Просмотреть файл

@ -121,7 +121,7 @@ add_task(async function test_manifest_selection() {
}); });
add_task(async function test_load_favicon_invalid() { add_task(async function test_load_favicon_invalid() {
let observed = TestUtils.topicObserved("console-api-log-event", msg => { let observed = TestUtils.consoleMessageObserved(msg => {
return msg.wrappedJSObject.arguments[0].includes( return msg.wrappedJSObject.arguments[0].includes(
"Content type does not match expected" "Content type does not match expected"
); );
@ -147,7 +147,7 @@ add_task(async function test_load_favicon_invalid() {
}); });
add_task(async function test_load_favicon_invalid_redirect() { add_task(async function test_load_favicon_invalid_redirect() {
let observed = TestUtils.topicObserved("console-api-log-event", msg => { let observed = TestUtils.consoleMessageObserved(msg => {
return msg.wrappedJSObject.arguments[0].includes( return msg.wrappedJSObject.arguments[0].includes(
"Content type does not match expected" "Content type does not match expected"
); );

Просмотреть файл

@ -355,7 +355,7 @@ function parseStack(aStack) {
/** /**
* Format a frame coming from Components.stack such that it can be used by the * Format a frame coming from Components.stack such that it can be used by the
* Browser Console, via console-api-log-event notifications. * Browser Console, via ConsoleAPIStorage notifications.
* *
* @param {object} aFrame * @param {object} aFrame
* The stack frame from which to begin the walk. * The stack frame from which to begin the walk.
@ -363,7 +363,7 @@ function parseStack(aStack) {
* Maximum stack trace depth. Default is 0 - no depth limit. * Maximum stack trace depth. Default is 0 - no depth limit.
* @return {object[]} * @return {object[]}
* An array of {filename, lineNumber, functionName, language} objects. * An array of {filename, lineNumber, functionName, language} objects.
* These objects follow the same format as other console-api-log-event * These objects follow the same format as other ConsoleAPIStorage
* messages. * messages.
*/ */
function getStack(aFrame, aMaxDepth = 0) { function getStack(aFrame, aMaxDepth = 0) {
@ -522,8 +522,8 @@ function createMultiLineDumper(aLevel) {
} }
/** /**
* Send a Console API message. This function will send a console-api-log-event * Send a Console API message. This function will send a notification through
* notification through the nsIObserverService. * the nsIConsoleAPIStorage service.
* *
* @param {object} aConsole * @param {object} aConsole
* The instance of ConsoleAPI performing the logging. * The instance of ConsoleAPI performing the logging.
@ -584,7 +584,7 @@ function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {}) {
Ci.nsIConsoleAPIStorage Ci.nsIConsoleAPIStorage
); );
if (ConsoleAPIStorage) { if (ConsoleAPIStorage) {
ConsoleAPIStorage.recordEvent("jsm", null, consoleEvent); ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
} }
} }