зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1432803 - Prevent using timeline actor to get DOMContentLoaded and load timings. r=Honza
MozReview-Commit-ID: Kkn36gDFTKN --HG-- extra : rebase_source : e52861432f5651b864cada0c70568d504d883c6c
This commit is contained in:
Родитель
8780a97e4e
Коммит
a273cd43a5
|
@ -5,10 +5,12 @@
|
|||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { TimelineFront } = require("devtools/shared/fronts/timeline");
|
||||
const { ACTIVITY_TYPE, EVENTS } = require("../constants");
|
||||
const FirefoxDataProvider = require("./firefox-data-provider");
|
||||
|
||||
// To be removed once FF60 is deprecated
|
||||
loader.lazyRequireGetter(this, "TimelineFront", "devtools/shared/fronts/timeline", true);
|
||||
|
||||
class FirefoxConnector {
|
||||
constructor() {
|
||||
// Public methods
|
||||
|
@ -18,6 +20,7 @@ class FirefoxConnector {
|
|||
this.navigate = this.navigate.bind(this);
|
||||
this.displayCachedEvents = this.displayCachedEvents.bind(this);
|
||||
this.onDocLoadingMarker = this.onDocLoadingMarker.bind(this);
|
||||
this.onDocEvent = this.onDocEvent.bind(this);
|
||||
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
|
||||
this.setPreferences = this.setPreferences.bind(this);
|
||||
this.triggerActivity = this.triggerActivity.bind(this);
|
||||
|
@ -44,7 +47,7 @@ class FirefoxConnector {
|
|||
actions: this.actions,
|
||||
});
|
||||
|
||||
this.addListeners();
|
||||
await this.addListeners();
|
||||
|
||||
// Listener for `will-navigate` event is (un)registered outside
|
||||
// of the `addListeners` and `removeListeners` methods since
|
||||
|
@ -54,64 +57,71 @@ class FirefoxConnector {
|
|||
this.tabTarget.on("will-navigate", this.willNavigate);
|
||||
this.tabTarget.on("navigate", this.navigate);
|
||||
|
||||
// Don't start up waiting for timeline markers if the server isn't
|
||||
// recent enough to emit the markers we're interested in.
|
||||
if (this.tabTarget.getTrait("documentLoadingMarkers")) {
|
||||
this.timelineFront = new TimelineFront(this.tabTarget.client, this.tabTarget.form);
|
||||
this.timelineFront.on("doc-loading", this.onDocLoadingMarker);
|
||||
await this.timelineFront.start({ withDocLoadingEvents: true });
|
||||
}
|
||||
|
||||
this.displayCachedEvents();
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
this.actions.batchReset();
|
||||
|
||||
this.removeListeners();
|
||||
await this.removeListeners();
|
||||
|
||||
if (this.tabTarget) {
|
||||
// Unregister `will-navigate` needs to be done before `this.timelineFront.destroy()`
|
||||
// since this.tabTarget might be nullified after timelineFront.destroy().
|
||||
this.tabTarget.off("will-navigate");
|
||||
// The timeline front wasn't initialized and started if the server wasn't
|
||||
// recent enough to emit the markers we were interested in.
|
||||
if (this.tabTarget.getTrait("documentLoadingMarkers") && this.timelineFront) {
|
||||
this.timelineFront.off("doc-loading", this.onDocLoadingMarker);
|
||||
await this.timelineFront.destroy();
|
||||
}
|
||||
this.tabTarget = null;
|
||||
}
|
||||
|
||||
this.webConsoleClient = null;
|
||||
this.timelineFront = null;
|
||||
this.dataProvider = null;
|
||||
this.panel = null;
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.removeListeners();
|
||||
async pause() {
|
||||
await this.removeListeners();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.addListeners();
|
||||
async resume() {
|
||||
await this.addListeners();
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
async addListeners() {
|
||||
this.tabTarget.on("close", this.disconnect);
|
||||
this.webConsoleClient.on("networkEvent",
|
||||
this.dataProvider.onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate",
|
||||
this.dataProvider.onNetworkEventUpdate);
|
||||
this.webConsoleClient.on("documentEvent", this.onDocEvent);
|
||||
|
||||
// With FF60+ console actor supports listening to document events like
|
||||
// DOMContentLoaded and load. We used to query Timeline actor, but it was too CPU
|
||||
// intensive.
|
||||
let { startedListeners } = await this.webConsoleClient.startListeners(
|
||||
["DocumentEvents"]);
|
||||
// Allows to know if we are on FF60 and support these events.
|
||||
let supportsDocEvents = startedListeners.includes("DocumentEvents");
|
||||
|
||||
// Don't start up waiting for timeline markers if the server isn't
|
||||
// recent enough (<FF45) to emit the markers we're interested in.
|
||||
if (!supportsDocEvents && !this.timelineFront &&
|
||||
this.tabTarget.getTrait("documentLoadingMarkers")) {
|
||||
this.timelineFront = new TimelineFront(this.tabTarget.client, this.tabTarget.form);
|
||||
this.timelineFront.on("doc-loading", this.onDocLoadingMarker);
|
||||
await this.timelineFront.start({ withDocLoadingEvents: true });
|
||||
}
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
async removeListeners() {
|
||||
if (this.tabTarget) {
|
||||
this.tabTarget.off("close");
|
||||
}
|
||||
if (this.timelineFront) {
|
||||
this.timelineFront.off("doc-loading", this.onDocLoadingMarker);
|
||||
await this.timelineFront.destroy();
|
||||
this.timelineFront = null;
|
||||
}
|
||||
if (this.webConsoleClient) {
|
||||
this.webConsoleClient.off("networkEvent");
|
||||
this.webConsoleClient.off("networkEventUpdate");
|
||||
this.webConsoleClient.off("docEvent");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,11 +186,32 @@ class FirefoxConnector {
|
|||
/**
|
||||
* The "DOMContentLoaded" and "Load" events sent by the timeline actor.
|
||||
*
|
||||
* To be removed once FF60 is deprecated.
|
||||
*
|
||||
* @param {object} marker
|
||||
*/
|
||||
onDocLoadingMarker(marker) {
|
||||
window.emit(EVENTS.TIMELINE_EVENT, marker);
|
||||
this.actions.addTimingMarker(marker);
|
||||
// Translate marker into event similar to newer "docEvent" event sent by the console
|
||||
// actor
|
||||
let event = {
|
||||
name: marker.name == "document::DOMContentLoaded" ?
|
||||
"dom-interactive" : "dom-complete",
|
||||
time: marker.unixTime / 1000
|
||||
};
|
||||
window.emit(EVENTS.TIMELINE_EVENT, event);
|
||||
this.actions.addTimingMarker(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* The "DOMContentLoaded" and "Load" events sent by the console actor.
|
||||
*
|
||||
* Only used by FF60+.
|
||||
*
|
||||
* @param {object} marker
|
||||
*/
|
||||
onDocEvent(type, event) {
|
||||
window.emit(EVENTS.TIMELINE_EVENT, event);
|
||||
this.actions.addTimingMarker(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,7 @@ class Connector {
|
|||
|
||||
// Connect/Disconnect API
|
||||
|
||||
connect(connection, actions, getState) {
|
||||
async connect(connection, actions, getState) {
|
||||
if (!connection || !connection.tab) {
|
||||
return;
|
||||
}
|
||||
|
@ -38,10 +38,10 @@ class Connector {
|
|||
let { clientType } = connection.tab;
|
||||
switch (clientType) {
|
||||
case "chrome":
|
||||
this.connectChrome(connection, actions, getState);
|
||||
await this.connectChrome(connection, actions, getState);
|
||||
break;
|
||||
case "firefox":
|
||||
this.connectFirefox(connection, actions, getState);
|
||||
await this.connectFirefox(connection, actions, getState);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unknown client type - ${clientType}`);
|
||||
|
@ -54,20 +54,20 @@ class Connector {
|
|||
|
||||
connectChrome(connection, actions, getState) {
|
||||
this.connector = require("./chrome-connector");
|
||||
this.connector.connect(connection, actions, getState);
|
||||
return this.connector.connect(connection, actions, getState);
|
||||
}
|
||||
|
||||
connectFirefox(connection, actions, getState) {
|
||||
this.connector = require("./firefox-connector");
|
||||
this.connector.connect(connection, actions, getState);
|
||||
return this.connector.connect(connection, actions, getState);
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.connector.pause();
|
||||
return this.connector.pause();
|
||||
}
|
||||
|
||||
resume() {
|
||||
this.connector.resume();
|
||||
return this.connector.resume();
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
|
|
@ -20,15 +20,15 @@ function TimingMarkers() {
|
|||
function addTimingMarker(state, action) {
|
||||
state = { ...state };
|
||||
|
||||
if (action.marker.name === "document::DOMContentLoaded" &&
|
||||
if (action.marker.name === "dom-interactive" &&
|
||||
state.firstDocumentDOMContentLoadedTimestamp === -1) {
|
||||
state.firstDocumentDOMContentLoadedTimestamp = action.marker.unixTime / 1000;
|
||||
state.firstDocumentDOMContentLoadedTimestamp = action.marker.time;
|
||||
return state;
|
||||
}
|
||||
|
||||
if (action.marker.name === "document::Load" &&
|
||||
if (action.marker.name === "dom-complete" &&
|
||||
state.firstDocumentLoadTimestamp === -1) {
|
||||
state.firstDocumentLoadTimestamp = action.marker.unixTime / 1000;
|
||||
state.firstDocumentLoadTimestamp = action.marker.time;
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,9 +23,9 @@ add_task(function* () {
|
|||
|
||||
ok(true, "Reloading finished");
|
||||
|
||||
is(markers[0].name, "document::DOMContentLoaded",
|
||||
is(markers[0].name, "dom-interactive",
|
||||
"The first received marker is correct.");
|
||||
is(markers[1].name, "document::Load",
|
||||
is(markers[1].name, "dom-complete",
|
||||
"The second received marker is correct.");
|
||||
|
||||
return teardown(monitor);
|
||||
|
|
|
@ -40,6 +40,7 @@ if (isWorker) {
|
|||
loader.lazyRequireGetter(this, "ConsoleServiceListener", "devtools/server/actors/webconsole/listeners", true);
|
||||
loader.lazyRequireGetter(this, "ConsoleReflowListener", "devtools/server/actors/webconsole/listeners", true);
|
||||
loader.lazyRequireGetter(this, "ContentProcessListener", "devtools/server/actors/webconsole/listeners", true);
|
||||
loader.lazyRequireGetter(this, "DocumentEventsListener", "devtools/server/actors/webconsole/listeners", true);
|
||||
}
|
||||
|
||||
function isObject(value) {
|
||||
|
@ -694,6 +695,16 @@ WebConsoleActor.prototype =
|
|||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
case "DocumentEvents":
|
||||
// Workers don't support this message type
|
||||
if (isWorker) {
|
||||
break;
|
||||
}
|
||||
if (!this.documentEventsListener) {
|
||||
this.documentEventsListener = new DocumentEventsListener(this);
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -786,6 +797,13 @@ WebConsoleActor.prototype =
|
|||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
case "DocumentEvents":
|
||||
if (this.documentEventsListener) {
|
||||
this.documentEventsListener.destroy();
|
||||
this.documentEventsListener = null;
|
||||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ XPCOMUtils.defineLazyServiceGetter(this,
|
|||
"swm",
|
||||
"@mozilla.org/serviceworkers/manager;1",
|
||||
"nsIServiceWorkerManager");
|
||||
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
||||
|
||||
// Process script used to forward console calls from content processes to parent process
|
||||
const CONTENT_PROCESS_SCRIPT = "resource://devtools/server/actors/webconsole/content-process-forward.js";
|
||||
|
@ -490,3 +491,83 @@ ContentProcessListener.prototype = {
|
|||
this.listener = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Forward `DOMContentLoaded` and `load` events with precise timing
|
||||
* of when events happened according to window.performance numbers.
|
||||
*
|
||||
* @constructor
|
||||
* @param object console
|
||||
* The web console actor.
|
||||
*/
|
||||
function DocumentEventsListener(console) {
|
||||
this.console = console;
|
||||
|
||||
this.onWindowReady = this.onWindowReady.bind(this);
|
||||
this.onContentLoaded = this.onContentLoaded.bind(this);
|
||||
this.onLoad = this.onLoad.bind(this);
|
||||
this.listen();
|
||||
}
|
||||
|
||||
exports.DocumentEventsListener = DocumentEventsListener;
|
||||
|
||||
DocumentEventsListener.prototype = {
|
||||
listen() {
|
||||
EventEmitter.on(this.console.parentActor, "window-ready", this.onWindowReady);
|
||||
this.onWindowReady({ window: this.console.window, isTopLevel: true });
|
||||
},
|
||||
|
||||
onWindowReady({ window, isTopLevel }) {
|
||||
// Ignore iframes
|
||||
if (!isTopLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { readyState } = window.document;
|
||||
if (readyState != "interactive" && readyState != "complete") {
|
||||
window.addEventListener("DOMContentLoaded", this.onContentLoaded, { once: true });
|
||||
} else {
|
||||
this.onContentLoaded({ target: window.document });
|
||||
}
|
||||
if (readyState != "complete") {
|
||||
window.addEventListener("load", this.onLoad, { once: true });
|
||||
} else {
|
||||
this.onLoad({ target: window.document });
|
||||
}
|
||||
},
|
||||
|
||||
onContentLoaded(event) {
|
||||
let window = event.target.defaultView;
|
||||
let packet = {
|
||||
from: this.console.actorID,
|
||||
type: "documentEvent",
|
||||
name: "dom-interactive",
|
||||
// milliseconds since the UNIX epoch, when the parser finished its work
|
||||
// on the main document, that is when its Document.readyState changes to
|
||||
// 'interactive' and the corresponding readystatechange event is thrown
|
||||
time: window.performance.timing.domInteractive
|
||||
};
|
||||
this.console.conn.send(packet);
|
||||
},
|
||||
|
||||
onLoad(event) {
|
||||
let window = event.target.defaultView;
|
||||
let packet = {
|
||||
from: this.console.actorID,
|
||||
type: "documentEvent",
|
||||
name: "dom-complete",
|
||||
// milliseconds since the UNIX epoch, when the parser finished its work
|
||||
// on the main document, that is when its Document.readyState changes to
|
||||
// 'complete' and the corresponding readystatechange event is thrown
|
||||
time: window.performance.timing.domComplete
|
||||
};
|
||||
this.console.conn.send(packet);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
EventEmitter.off(this.console.parentActor, "window-ready", this.onWindowReady);
|
||||
|
||||
this.listener = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ const UnsolicitedNotifications = {
|
|||
"logMessage": "logMessage",
|
||||
"networkEvent": "networkEvent",
|
||||
"networkEventUpdate": "networkEventUpdate",
|
||||
"documentEvent": "documentEvent",
|
||||
"newGlobal": "newGlobal",
|
||||
"newScript": "newScript",
|
||||
"tabDetached": "tabDetached",
|
||||
|
|
|
@ -33,11 +33,13 @@ function WebConsoleClient(debuggerClient, response) {
|
|||
this.onNetworkEvent = this._onNetworkEvent.bind(this);
|
||||
this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
|
||||
this.onInspectObject = this._onInspectObject.bind(this);
|
||||
this.onDocEvent = this._onDocEvent.bind(this);
|
||||
|
||||
this._client.addListener("evaluationResult", this.onEvaluationResult);
|
||||
this._client.addListener("networkEvent", this.onNetworkEvent);
|
||||
this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
|
||||
this._client.addListener("inspectObject", this.onInspectObject);
|
||||
this._client.addListener("documentEvent", this.onDocEvent);
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
|
@ -187,6 +189,20 @@ WebConsoleClient.prototype = {
|
|||
this.emit("inspectObject", packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "docEvent" message type handler. We just re-emit it so that
|
||||
* the tools can listen for them on the console client.
|
||||
*
|
||||
* @private
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onDocEvent: function (type, packet) {
|
||||
this.emit("documentEvent", packet);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the cached messages from the server.
|
||||
*
|
||||
|
@ -676,6 +692,7 @@ WebConsoleClient.prototype = {
|
|||
this._client.removeListener("networkEventUpdate",
|
||||
this.onNetworkEventUpdate);
|
||||
this._client.removeListener("inspectObject", this.onInspectObject);
|
||||
this._client.removeListener("documentEvent", this.onDocEvent);
|
||||
this.stopListeners(null, onResponse);
|
||||
this._longStrings = null;
|
||||
this._client = null;
|
||||
|
|
Загрузка…
Ссылка в новой задаче