--- a/devtools/client/netmonitor/src/connector/chrome-connector.js
+++ b/devtools/client/netmonitor/src/connector/chrome-connector.js
@@ -1,17 +1,21 @@
/* 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/. */
"use strict";
const { ACTIVITY_TYPE } = require("../constants");
-const { CDPConnector } = require("./chrome/events");
+const { CDPConnector } = require("./chrome/cdp-connector");
+/**
+ * This object is responsible for connecting the Network monitor
+ * panel with Chrome browser.
+ */
class ChromeConnector {
constructor() {
// Internal properties
this.payloadQueue = [];
this.connector = undefined;
// Public methods
this.connect = this.connect.bind(this);
@@ -31,17 +35,18 @@ class ChromeConnector {
this.connector.willNavigate(this.willNavigate);
}
async disconnect() {
this.connector.disconnect();
}
/**
- * currently all events are about "navigation" is not support on CDP
+ * Remove all requests upon navigation.
+ * TODO: support 'Persist Log'
*/
willNavigate() {
this.actions.batchReset();
this.actions.clearRequests();
}
/**
* Triggers a specific "activity" to be performed by the frontend.
@@ -69,21 +74,21 @@ class ChromeConnector {
/**
* Send a HTTP request data payload
*
* @param {object} data data payload would like to sent to backend
* @param {function} callback callback will be invoked after the request finished
*/
sendHTTPRequest(data, callback) {
- // TODO : not support. currently didn't provide this feature in CDP API.
+ // TODO: not supported currently in CDP API.
}
setPreferences() {
- // TODO : implement.
+ // TODO
}
viewSourceInDebugger() {
- // TODO : implement.
+ // TODO
}
}
module.exports = new ChromeConnector();
--- a/devtools/client/netmonitor/src/connector/chrome/bulk-loader.js
+++ b/devtools/client/netmonitor/src/connector/chrome/bulk-loader.js
@@ -1,17 +1,124 @@
/* 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/. */
"use strict";
-let bulkLoader = undefined;
+let PriorityLevels = {
+ Critical: 1,
+ Major: 2,
+ Normal: 3,
+ None: 0
+};
+
+let bulkLoader;
+
+/**
+ * In order to reduce the frequency of network request update, bulk-loader
+ * is introducing a task schedule mechanism to queue and delay the upcoming
+ * network request. This is helpful to speed up performance and mitigate UI
+ * frozen when loading large website.
+ *
+ * BulkLoader is implemented as a singleton.
+ */
+const getBulkLoader = () => {
+ if (!bulkLoader) {
+ bulkLoader = new BulkLoader();
+ }
+ return bulkLoader;
+};
+
+// Helpers
+
+const mappingPriority = (priority, options) => {
+ switch (priority) {
+ case PriorityLevels.Critical:
+ return options.Critical;
+ case PriorityLevels.Major:
+ return options.Major;
+ case PriorityLevels.Normal:
+ return options.Normal;
+ case PriorityLevels.None:
+ default:
+ break;
+ }
+ return options.None;
+};
+
+const getTimeoutMS = (priority) => {
+ const delay = {
+ Critical: 3000,
+ Major: 1000,
+ Normal: 500,
+ None: 100
+ };
+ return mappingPriority(priority, delay);
+};
-let PriorityLevels = {Critical: 1, Major: 2, Normal: 3, None: 0};
+const getDelayStartMS = (priority) => {
+ const delay = {
+ Critical: 1,
+ Major: 50,
+ Normal: 100,
+ None: 500
+ };
+ return mappingPriority(priority, delay);
+};
+
+const LoaderPromise = (priority, callback) => {
+ return new Promise((resolve, reject) => {
+ const ms = getTimeoutMS(priority);
+ // Set up the real work
+ setTimeout(() => callback(resolve, reject), getDelayStartMS(priority));
+
+ // Set up the timeout
+ setTimeout(() => {
+ reject("Promise timed out after " + ms + " ms");
+ }, ms);
+ });
+};
+
+// TODO: recovery thread after all tasks finished.
+class Thread {
+ constructor() {
+ this.scheduler = new Scheduler();
+ }
+
+ addTask(callback, priority) {
+ this.scheduler.sync(() => {
+ return LoaderPromise(
+ !priority ? PriorityLevels.None : priority,
+ (resolve, reject) => callback(resolve, reject)
+ );
+ });
+ }
+}
+
+class BulkLoader {
+ constructor() {
+ this.threads = new Map();
+ this.tasks = [];
+ }
+
+ add(id, callback, priority) {
+ let thread = this.threads.get(priority);
+ if (!this.threads.has(priority)) {
+ thread = new Thread();
+ this.threads.set(priority, thread);
+ }
+ this.tasks.push({id, priority, task: callback, isFinished: false});
+ return thread.addTask(callback, priority);
+ }
+
+ reset() {
+ this.threads.clear();
+ }
+}
class Scheduler {
constructor() {
this.busy = false;
this.queue = [];
}
sync(task) {
@@ -22,105 +129,24 @@ class Scheduler {
return null;
}
dequeue() {
let self = this;
let recursive = (resolve) => {
self.dequeue();
};
+
this.busy = true;
+
let next = this.queue.shift();
if (next) {
next().then(recursive, recursive);
} else {
this.busy = false;
}
}
}
-// singleton class
-const getBulkLoader = () => {
- const mappingPriority = (priority, options) => {
- switch (priority) {
- case PriorityLevels.Critical:
- return options.Critical;
- case PriorityLevels.Major:
- return options.Major;
- case PriorityLevels.Normal:
- return options.Normal;
- case PriorityLevels.None:
- default:
- break;
- }
- return options.None;
- };
-
- const getTimeoutMS = (priority) => {
- const delay = {Critical: 3000, Major: 1000, Normal: 500, None: 100};
- return mappingPriority(priority, delay);
- };
-
- const getDelayStartMS = (priority) => {
- const delay = {Critical: 1, Major: 50, Normal: 100, None: 500};
- return mappingPriority(priority, delay);
- };
-
- const LoaderPromise = (priority, callback) => {
- return new Promise((resolve, reject) => {
- const ms = getTimeoutMS(priority);
- // Set up the real work
- setTimeout(() => callback(resolve, reject), getDelayStartMS(priority));
-
- // Set up the timeout
- setTimeout(() => {
- reject("Promise timed out after " + ms + " ms");
- }, ms);
- });
- };
-
- // TODO : recovery thread after all tasks finished.
- class Thread {
- constructor() {
- this.scheduler = new Scheduler();
- }
-
- addTask(callback, priority) {
- this.scheduler.sync(() => {
- return LoaderPromise(
- !priority ? PriorityLevels.None : priority,
- (resolve, reject) => callback(resolve, reject)
- );
- });
- }
- }
-
- class BulkLoader {
- constructor() {
- this.threads = new Map();
- this.tasks = [];
- }
-
- add(id, callback, priority) {
- let thread = this.threads.get(priority);
- if (!this.threads.has(priority)) {
- thread = new Thread();
- this.threads.set(priority, thread);
- }
- this.tasks.push({id, priority, task: callback, isFinished: false});
- return thread.addTask(callback, priority);
- }
-
- reset() {
- this.threads.clear();
- }
- }
-
- if (!bulkLoader) {
- bulkLoader = new BulkLoader();
- }
-
- return bulkLoader;
-};
module.exports = {
getBulkLoader,
PriorityLevels
};
rename from devtools/client/netmonitor/src/connector/chrome/events.js
rename to devtools/client/netmonitor/src/connector/chrome/cdp-connector.js
--- a/devtools/client/netmonitor/src/connector/chrome/events.js
+++ b/devtools/client/netmonitor/src/connector/chrome/cdp-connector.js
@@ -1,38 +1,47 @@
/* 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/. */
"use strict";
-const {EVENTS} = require("../../constants");
-const {Payloads} = require("./utils");
-const {getBulkLoader, PriorityLevels} = require("./bulk-loader");
+
+const { EVENTS } = require("../../constants");
+const { Payloads } = require("./payloads");
+const { getBulkLoader, PriorityLevels } = require("./bulk-loader");
+/**
+ * CDPConnector handles Network and Page events from Chrome remote debug protocol.
+ * It's using helpers from payloads.js and bulk-loader.js when received request/response
+ * data from CDP.
+ */
class CDPConnector {
constructor() {
this.payloads = new Payloads();
this.onNetworkUpdate = this.onNetworkUpdate.bind(this);
this.onResponseReceived = this.onResponseReceived.bind(this);
this.onDataReceived = this.onDataReceived.bind(this);
this.onLoadingFinished = this.onLoadingFinished.bind(this);
this.onLoadingFailed = this.onLoadingFailed.bind(this);
this.update = this.update.bind(this);
}
setup(connection, actions) {
let {Network, Page} = connection;
this.Network = Network;
this.Page = Page;
this.actions = actions;
+
+ // Add event listeners
Network.requestWillBeSent(this.onNetworkUpdate);
Network.responseReceived(this.onResponseReceived);
Network.dataReceived(this.onDataReceived);
Network.loadingFinished(this.onLoadingFinished);
Network.loadingFailed(this.onLoadingFailed);
+
Network.enable();
Page.enable();
}
disconnect() {
this.Network.disable();
this.Page.disable();
this.payloads.clear();
@@ -44,17 +53,17 @@ class CDPConnector {
this.Page.disable(),
this.payloads.clear(),
this.Network.enable(),
this.Page.enable()
]);
}
willNavigate(event) {
- // not support
+ // TODO
}
onNetworkUpdate(params) {
let {requestId} = params;
let payload = this.payloads.add(requestId);
return payload.update(params).then(
([request, header, postData]) => {
let bulkloader = getBulkLoader();
@@ -79,35 +88,36 @@ class CDPConnector {
loader.add(
requestId,
(resolve) => {
this.updateResponseHeader(requestId, header);
this.updateResponseState(requestId, state);
this.updateResponseTiming(requestId, timings);
this.getResponseContent(params);
resolve();
- }
- , PriorityLevels.Major);
+ },
+ PriorityLevels.Major
+ );
});
}
onDataReceived(params) {
let {requestId} = params;
let payload = this.payloads.get(requestId);
payload.update(params);
}
onLoadingFinished(params) {
let {requestId} = params;
let payload = this.payloads.get(requestId);
if (payload) {
payload.log("LoadingFinished", params);
}
+
// TODO: verify getCookie method.
- //
}
updateRequestHeader(requestId, header) {
if (!header) {
return;
}
this.update(requestId, {
requestHeaders: header
@@ -145,17 +155,16 @@ class CDPConnector {
}
onLoadingFailed(params) {
let {requestId} = params;
let payload = this.payloads.get(requestId);
if (payload) {
payload.log("LoadingFailed", params);
}
- // console.log(params.requestId);
}
async getResponseContent(params) {
let {requestId, response} = params;
return this.Network.getResponseBody({requestId}).then(
(content) => {
let payload = this.payloads.get(requestId);
@@ -175,21 +184,19 @@ class CDPConnector {
}
);
}
updateResponseContent(requestId, payload) {
if (!payload) {
return;
}
- this.actions.updateRequest(requestId, payload, true).then(
- () => {
- window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, requestId);
- }
- );
+ this.actions.updateRequest(requestId, payload, true).then(() => {
+ window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, requestId);
+ });
}
updatePostData(requestId, postData) {
if (!postData) {
return;
}
this.update(requestId, {
requestPostData: postData,
@@ -208,28 +215,23 @@ class CDPConnector {
url,
isXHR,
cause,
startedDateTime,
fromCache,
fromServiceWorker,
} = data;
- this.actions.addRequest(
- id,
- {
- startedMillis: startedDateTime,
- method,
- url,
- isXHR,
- cause,
- fromCache,
- fromServiceWorker,
- },
- true,
- )
- .then(() => window.emit(EVENTS.REQUEST_ADDED, id));
+ this.actions.addRequest(id, {
+ startedMillis: startedDateTime,
+ method,
+ url,
+ isXHR,
+ cause,
+ fromCache,
+ fromServiceWorker,
+ }, true).then(() => window.emit(EVENTS.REQUEST_ADDED, id));
}
}
module.exports = {
CDPConnector
};
rename from devtools/client/netmonitor/src/connector/chrome/utils.js
rename to devtools/client/netmonitor/src/connector/chrome/payloads.js
--- a/devtools/client/netmonitor/src/connector/chrome/utils.js
+++ b/devtools/client/netmonitor/src/connector/chrome/payloads.js
@@ -1,45 +1,55 @@
/* 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/. */
"use strict";
-const { Request, Header, PostData } = require("./request");
-const { State, ResponseContent, Timings} = require("./response");
+const { Request, Header, PostData } = require("./request-utils");
+const { State, ResponseContent, Timings} = require("./response-utils");
const { getBulkLoader } = require("./bulk-loader");
+/**
+ * Payload collects data received over CDP.
+ */
class Payload {
constructor() {
this.payload = {};
this.update = this.update.bind(this);
}
+
async update(payload) {
- let { request, response, requestId, timestamp,
- content, dataLength, encodedDataLength } = payload;
+ let {
+ request,
+ response,
+ requestId,
+ timestamp,
+ content,
+ dataLength,
+ encodedDataLength
+ } = payload;
+
let {
headers,
postData,
timing
} = (request ? request : response) || {};
const header = await this.mappingHeader(requestId, headers);
this.requestId = requestId;
+ this.updateTimestamp(timestamp);
- this.updateTimestamp(timestamp);
- let data = await this.mappingAll(
- requestId,
- {
- payload, response, postData,
- header, content, timing,
- dataLength, encodedDataLength
- }
- );
+ let data = await this.mappingAll(requestId, {
+ payload, response, postData,
+ header, content, timing,
+ dataLength, encodedDataLength
+ });
+
return data;
}
log(reason, info) {
this.updatePayload({
type: reason,
log: info
});
@@ -52,33 +62,54 @@ class Payload {
);
}
updatePayload(data) {
this.payload = Object.assign({}, this.payload, data);
}
async mappingAll(requestId, data) {
- let {payload, response, postData,
- header, content, timing,
- dataLength, encodedDataLength } = data;
- let [requests, headers, post,
- status, timings, responses]
- = await Promise.all(
- [
- this.mappingRequest(requestId, payload),
- header,
- this.mappingRequestPostData(requestId, postData, header),
- this.mappingResponseStatus(requestId, response, header),
- this.mappingTiming(requestId, timing),
- this.mappingResponseContent(requestId, response, content)
- ]);
+ let {
+ payload,
+ response,
+ postData,
+ header,
+ content,
+ timing,
+ dataLength,
+ encodedDataLength
+ } = data;
+
+ let [
+ requests,
+ headers,
+ post,
+ status,
+ timings,
+ responses
+ ] = await Promise.all([
+ this.mappingRequest(requestId, payload),
+ header,
+ this.mappingRequestPostData(requestId, postData, header),
+ this.mappingResponseStatus(requestId, response, header),
+ this.mappingTiming(requestId, timing),
+ this.mappingResponseContent(requestId, response, content)
+ ]);
+
this.updatePayload({
- requests, headers, post, status, timings, responses, dataLength, encodedDataLength
+ requests,
+ headers,
+ post,
+ status,
+ timings,
+ responses,
+ dataLength,
+ encodedDataLength
});
+
return [ requests, headers, post, status, timings, responses ];
}
async mappingTiming(requestId, timing) {
return !timing ? undefined : Timings(requestId, timing);
}
async mappingRequest(requestId, payload) {
@@ -98,16 +129,17 @@ class Payload {
return !response ? undefined : State(response, header);
}
async mappingResponseContent(requestId, response, content) {
return !response || !content ?
undefined : ResponseContent(requestId, response, content);
}
}
+
class Payloads {
constructor() {
this.payloads = new Map();
}
add(id) {
if (!this.payloads.has(id)) {
this.payloads.set(id, new Payload());
@@ -123,10 +155,10 @@ class Payloads {
clear() {
this.payloads.clear();
let loader = getBulkLoader();
loader.reset();
}
}
module.exports = {
- Payload, Payloads
+ Payloads
};
rename from devtools/client/netmonitor/src/connector/chrome/request.js
rename to devtools/client/netmonitor/src/connector/chrome/request-utils.js
--- a/devtools/client/netmonitor/src/connector/chrome/request.js
+++ b/devtools/client/netmonitor/src/connector/chrome/request-utils.js
@@ -26,77 +26,87 @@ function mappingCallFrames(callFrames) {
}
);
return stacktrace;
}
function Cause(initiator) {
let {url, type, stack} = initiator;
let {callFrames} = stack || {};
+
if (!stack || !callFrames.length) {
return undefined;
}
+
let cause = {
type: type,
loadingDocumentUri: url,
stacktrace: mappingCallFrames(callFrames)
};
+
return cause;
}
function Header(id, headers) {
let header = [];
let headersSize = 0;
+
Object.keys(headers).map((value) => {
- header.push(
- {
- name: value,
- value: headers[value],
- }
- );
+ header.push({
+ name: value,
+ value: headers[value],
+ });
+
headersSize += value.length + headers[value].length;
});
return {
from: id,
headers: header,
headersSize: headersSize,
rawHeaders: undefined,
};
}
+
function PostData(id, postData, header) {
let {headers, headersSize} = header;
- let payload = {},
- requestPostData = {
- from: id, postDataDiscarded: false, postData: {}
- };
+ let payload = {};
+ let requestPostData = {
+ from: id,
+ postDataDiscarded: false,
+ postData: {}
+ };
+
if (postData) {
requestPostData.postData.text = postData;
payload.requestPostData = Object.assign({}, requestPostData);
payload.requestHeadersFromUploadStream = {headers, headersSize};
}
+
return payload;
}
/**
- * Not support on current version.
+ * Not supported by the current protocol version.
* unstable method: Network.getCookies
* cause: https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-getCookies
*/
function Cookie(id, Network) {
// TODO: verify
}
function Request(id, requestData) {
let {request, initiator, timestamp} = requestData;
let {url, method} = request;
let cause = !initiator ? undefined : Cause(initiator);
+
return {
method, url, cause,
- isXHR: false, // TODO: verify
+ // TODO: verify
+ isXHR: false,
startedDateTime: timestamp,
fromCache: undefined,
fromServiceWorker: undefined
};
}
module.exports = {
Cause,
rename from devtools/client/netmonitor/src/connector/chrome/response.js
rename to devtools/client/netmonitor/src/connector/chrome/response-utils.js
--- a/devtools/client/netmonitor/src/connector/chrome/response.js
+++ b/devtools/client/netmonitor/src/connector/chrome/response-utils.js
@@ -5,98 +5,108 @@
"use strict";
const { formDataURI } = require("../../utils/request-utils");
function ResponseInfo(id, response, content) {
let {
mimeType
} = response;
- const {body, base64Encoded} = content;
+
+ const {
+ body,
+ base64Encoded
+ } = content;
+
return {
from: id,
content: {
mimeType: mimeType,
text: !body ? "" : body,
size: !body ? 0 : body.length,
encoding: base64Encoded ? "base64" : undefined
}
};
}
function ResponseContent(id, response, content) {
const {body, base64Encoded} = content;
let {mimeType, encodedDataLength} = response;
let responseContent = ResponseInfo(id, response, content);
- let payload = Object.assign(
- {
- responseContent,
- contentSize: !body ? 0 : body.length,
- transferredSize: encodedDataLength, // TODO: verify
- mimeType: mimeType
- }, body);
+ let payload = Object.assign({
+ responseContent,
+ contentSize: !body ? 0 : body.length,
+ transferredSize: encodedDataLength, // TODO: verify
+ mimeType: mimeType
+ }, body);
+
if (mimeType.includes("image/")) {
payload.responseContentDataUri = formDataURI(mimeType, base64Encoded, response);
}
+
return payload;
}
/**
- * Not support on current version.
+ * Not supported by the current protocol version.
* unstable method: Security
* cause: https://chromedevtools.github.io/devtools-protocol/tot/Security/
*/
function SecurityDetails(id, security) {
// TODO : verify
-
return {};
}
function Timings(id, timing) {
- // TODO : implement
+ // TODO: implement
let {
dnsStart,
dnsEnd,
connectStart,
connectEnd,
sendStart,
sendEnd,
receiveHeadersEnd
} = timing;
+
let dns = parseInt(dnsEnd - dnsStart, 10);
let connect = parseInt(connectEnd - connectStart, 10);
let send = parseInt(sendEnd - sendStart, 10);
let total = parseInt(receiveHeadersEnd, 10);
+
return {
from: id,
timings: {
blocked: 0,
dns: dns,
connect: connect,
send: send,
wait: parseInt(receiveHeadersEnd - (send + connect + dns), 10),
receive: 0,
},
totalTime: total,
};
}
+
function State(response, headers) {
let { headersSize } = headers;
let {
status,
statusText,
remoteIPAddress,
remotePort
} = response;
+
return {
remoteAddress: remoteIPAddress,
remotePort,
status,
statusText,
headersSize
};
}
+
module.exports = {
State,
Timings,
ResponseContent,
SecurityDetails
};