зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1690221 - [devtools] Merge FirefoxConnector into Connector. r=bomsy
Differential Revision: https://phabricator.services.mozilla.com/D103750
This commit is contained in:
Родитель
1e919fbc8d
Коммит
a212b7b7b5
|
@ -68,11 +68,7 @@ NetMonitorAPI.prototype = {
|
|||
owner: this,
|
||||
};
|
||||
|
||||
await this.connector.connectFirefox(
|
||||
connection,
|
||||
this.actions,
|
||||
this.store.getState
|
||||
);
|
||||
await this.connector.connect(connection, this.actions, this.store.getState);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -193,9 +189,7 @@ NetMonitorAPI.prototype = {
|
|||
};
|
||||
|
||||
this.harExportConnector = new Connector();
|
||||
this.harExportConnectorReady = this.harExportConnector.connectFirefox(
|
||||
connection
|
||||
);
|
||||
this.harExportConnectorReady = this.harExportConnector.connect(connection);
|
||||
|
||||
await this.harExportConnectorReady;
|
||||
return this.harExportConnector;
|
||||
|
|
|
@ -1,618 +0,0 @@
|
|||
/* 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 Services = require("Services");
|
||||
const {
|
||||
ACTIVITY_TYPE,
|
||||
EVENTS,
|
||||
TEST_EVENTS,
|
||||
} = require("devtools/client/netmonitor/src/constants");
|
||||
const FirefoxDataProvider = require("devtools/client/netmonitor/src/connector/firefox-data-provider");
|
||||
const {
|
||||
getDisplayedTimingMarker,
|
||||
} = require("devtools/client/netmonitor/src/selectors/index");
|
||||
|
||||
// Network throttling
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"throttlingProfiles",
|
||||
"devtools/client/shared/components/throttling/profiles"
|
||||
);
|
||||
|
||||
/**
|
||||
* Connector to Firefox backend.
|
||||
*/
|
||||
class FirefoxConnector {
|
||||
constructor() {
|
||||
// Public methods
|
||||
this.connect = this.connect.bind(this);
|
||||
this.disconnect = this.disconnect.bind(this);
|
||||
this.willNavigate = this.willNavigate.bind(this);
|
||||
this.navigate = this.navigate.bind(this);
|
||||
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
|
||||
this.setPreferences = this.setPreferences.bind(this);
|
||||
this.triggerActivity = this.triggerActivity.bind(this);
|
||||
this.getTabTarget = this.getTabTarget.bind(this);
|
||||
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
|
||||
this.requestData = this.requestData.bind(this);
|
||||
this.getTimingMarker = this.getTimingMarker.bind(this);
|
||||
this.updateNetworkThrottling = this.updateNetworkThrottling.bind(this);
|
||||
|
||||
// Internals
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.onTargetAvailable = this.onTargetAvailable.bind(this);
|
||||
this.onResourceAvailable = this.onResourceAvailable.bind(this);
|
||||
this.onResourceUpdated = this.onResourceUpdated.bind(this);
|
||||
|
||||
this.networkFront = null;
|
||||
this.listenForNetworkEvents = true;
|
||||
}
|
||||
|
||||
get currentTarget() {
|
||||
return this.toolbox.targetList.targetFront;
|
||||
}
|
||||
|
||||
get hasResourceWatcherSupport() {
|
||||
return this.toolbox.resourceWatcher.hasResourceWatcherSupport(
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT
|
||||
);
|
||||
}
|
||||
|
||||
get watcherFront() {
|
||||
return this.toolbox.resourceWatcher.watcherFront;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the backend.
|
||||
*
|
||||
* @param {Object} connection object with e.g. reference to the Toolbox.
|
||||
* @param {Object} actions (optional) is used to fire Redux actions to update store.
|
||||
* @param {Object} getState (optional) is used to get access to the state.
|
||||
*/
|
||||
async connect(connection, actions, getState) {
|
||||
this.actions = actions;
|
||||
this.getState = getState;
|
||||
this.toolbox = connection.toolbox;
|
||||
|
||||
// The owner object (NetMonitorAPI) received all events.
|
||||
this.owner = connection.owner;
|
||||
|
||||
await this.toolbox.targetList.watchTargets(
|
||||
[this.toolbox.targetList.TYPES.FRAME],
|
||||
this.onTargetAvailable
|
||||
);
|
||||
|
||||
await this.toolbox.resourceWatcher.watchResources(
|
||||
[this.toolbox.resourceWatcher.TYPES.DOCUMENT_EVENT],
|
||||
{ onAvailable: this.onResourceAvailable }
|
||||
);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
// As this function might be called twice, we need to guard if already called.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._destroyed = true;
|
||||
|
||||
this.toolbox.targetList.unwatchTargets(
|
||||
[this.toolbox.targetList.TYPES.FRAME],
|
||||
this.onTargetAvailable
|
||||
);
|
||||
|
||||
this.toolbox.resourceWatcher.unwatchResources(
|
||||
[this.toolbox.resourceWatcher.TYPES.DOCUMENT_EVENT],
|
||||
{ onAvailable: this.onResourceAvailable }
|
||||
);
|
||||
|
||||
if (this.actions) {
|
||||
this.actions.batchReset();
|
||||
}
|
||||
|
||||
this.removeListeners();
|
||||
|
||||
this.currentTarget.off("will-navigate", this.willNavigate);
|
||||
|
||||
this.webConsoleFront = null;
|
||||
this.dataProvider = null;
|
||||
}
|
||||
|
||||
async pause() {
|
||||
this.listenForNetworkEvents = false;
|
||||
}
|
||||
|
||||
async resume() {
|
||||
this.listenForNetworkEvents = true;
|
||||
}
|
||||
|
||||
async onTargetAvailable({ targetFront, isTargetSwitching }) {
|
||||
if (!targetFront.isTopLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTargetSwitching) {
|
||||
this.willNavigate();
|
||||
}
|
||||
|
||||
// Listener for `will-navigate` event is (un)registered outside
|
||||
// of the `addListeners` and `removeListeners` methods since
|
||||
// these are used to pause/resume the connector.
|
||||
// Paused network panel should be automatically resumed when page
|
||||
// reload, so `will-navigate` listener needs to be there all the time.
|
||||
targetFront.on("will-navigate", this.willNavigate);
|
||||
|
||||
this.webConsoleFront = await this.currentTarget.getFront("console");
|
||||
|
||||
this.dataProvider = new FirefoxDataProvider({
|
||||
webConsoleFront: this.webConsoleFront,
|
||||
actions: this.actions,
|
||||
owner: this.owner,
|
||||
resourceWatcher: this.toolbox.resourceWatcher,
|
||||
});
|
||||
|
||||
// If this is the first top level target, lets register all the listeners
|
||||
if (!isTargetSwitching) {
|
||||
await this.addListeners();
|
||||
}
|
||||
|
||||
// Initialize Responsive Emulation front for network throttling.
|
||||
this.responsiveFront = await this.currentTarget.getFront("responsive");
|
||||
if (this.hasResourceWatcherSupport) {
|
||||
this.networkFront = await this.watcherFront.getNetworkParentActor();
|
||||
}
|
||||
}
|
||||
|
||||
async onResourceAvailable(resources) {
|
||||
for (const resource of resources) {
|
||||
const { TYPES } = this.toolbox.resourceWatcher;
|
||||
|
||||
if (resource.resourceType === TYPES.DOCUMENT_EVENT) {
|
||||
this.onDocEvent(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.listenForNetworkEvents) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.NETWORK_EVENT) {
|
||||
this.dataProvider.onNetworkResourceAvailable(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.NETWORK_EVENT_STACKTRACE) {
|
||||
this.dataProvider.onStackTraceAvailable(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.WEBSOCKET) {
|
||||
const { wsMessageType } = resource;
|
||||
|
||||
switch (wsMessageType) {
|
||||
case "webSocketOpened": {
|
||||
this.dataProvider.onWebSocketOpened(
|
||||
resource.httpChannelId,
|
||||
resource.effectiveURI,
|
||||
resource.protocols,
|
||||
resource.extensions
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "webSocketClosed": {
|
||||
this.dataProvider.onWebSocketClosed(
|
||||
resource.httpChannelId,
|
||||
resource.wasClean,
|
||||
resource.code,
|
||||
resource.reason
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "frameReceived": {
|
||||
this.dataProvider.onFrameReceived(
|
||||
resource.httpChannelId,
|
||||
resource.data
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "frameSent": {
|
||||
this.dataProvider.onFrameSent(
|
||||
resource.httpChannelId,
|
||||
resource.data
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.SERVER_SENT_EVENT) {
|
||||
const { messageType, httpChannelId, data } = resource;
|
||||
switch (messageType) {
|
||||
case "eventSourceConnectionClosed": {
|
||||
this.dataProvider.onEventSourceConnectionClosed(httpChannelId);
|
||||
break;
|
||||
}
|
||||
case "eventReceived": {
|
||||
this.dataProvider.onEventReceived(httpChannelId, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onResourceUpdated(updates) {
|
||||
for (const { resource, update } of updates) {
|
||||
if (
|
||||
resource.resourceType ===
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT &&
|
||||
this.listenForNetworkEvents
|
||||
) {
|
||||
this.dataProvider.onNetworkResourceUpdated(resource, update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addListeners(ignoreExistingResources = false) {
|
||||
const targetResources = [
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT,
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT_STACKTRACE,
|
||||
];
|
||||
if (Services.prefs.getBoolPref("devtools.netmonitor.features.webSockets")) {
|
||||
targetResources.push(this.toolbox.resourceWatcher.TYPES.WEBSOCKET);
|
||||
}
|
||||
|
||||
if (
|
||||
Services.prefs.getBoolPref(
|
||||
"devtools.netmonitor.features.serverSentEvents"
|
||||
)
|
||||
) {
|
||||
targetResources.push(
|
||||
this.toolbox.resourceWatcher.TYPES.SERVER_SENT_EVENT
|
||||
);
|
||||
}
|
||||
|
||||
await this.toolbox.resourceWatcher.watchResources(targetResources, {
|
||||
onAvailable: this.onResourceAvailable,
|
||||
onUpdated: this.onResourceUpdated,
|
||||
ignoreExistingResources,
|
||||
});
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.toolbox.resourceWatcher.unwatchResources(
|
||||
[
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT,
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT_STACKTRACE,
|
||||
this.toolbox.resourceWatcher.TYPES.WEBSOCKET,
|
||||
this.toolbox.resourceWatcher.TYPES.SERVER_SENT_EVENT,
|
||||
],
|
||||
{
|
||||
onAvailable: this.onResourceAvailable,
|
||||
onUpdated: this.onResourceUpdated,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
enableActions(enable) {
|
||||
this.dataProvider.enableActions(enable);
|
||||
}
|
||||
|
||||
willNavigate() {
|
||||
if (this.actions) {
|
||||
if (!Services.prefs.getBoolPref("devtools.netmonitor.persistlog")) {
|
||||
this.actions.batchReset();
|
||||
this.actions.clearRequests();
|
||||
} else {
|
||||
// If the log is persistent, just clear all accumulated timing markers.
|
||||
this.actions.clearTimingMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.actions && this.getState) {
|
||||
const state = this.getState();
|
||||
// Resume is done automatically on page reload/navigation.
|
||||
if (!state.requests.recording) {
|
||||
this.actions.toggleRecording();
|
||||
}
|
||||
|
||||
// Stop any ongoing search.
|
||||
if (state.search.ongoingSearch) {
|
||||
this.actions.stopOngoingSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
navigate() {
|
||||
if (!this.dataProvider.hasPendingRequests()) {
|
||||
this.onReloaded();
|
||||
return;
|
||||
}
|
||||
const listener = () => {
|
||||
if (this.dataProvider && this.dataProvider.hasPendingRequests()) {
|
||||
return;
|
||||
}
|
||||
if (this.owner) {
|
||||
this.owner.off(EVENTS.PAYLOAD_READY, listener);
|
||||
}
|
||||
// Netmonitor may already be destroyed,
|
||||
// so do not try to notify the listeners
|
||||
if (this.dataProvider) {
|
||||
this.onReloaded();
|
||||
}
|
||||
};
|
||||
if (this.owner) {
|
||||
this.owner.on(EVENTS.PAYLOAD_READY, listener);
|
||||
}
|
||||
}
|
||||
|
||||
onReloaded() {
|
||||
const panel = this.toolbox.getPanel("netmonitor");
|
||||
if (panel) {
|
||||
panel.emit("reloaded");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "DOMContentLoaded" and "Load" events sent by the console actor.
|
||||
*
|
||||
* @param {object} resource The DOCUMENT_EVENT resource
|
||||
*/
|
||||
onDocEvent(resource) {
|
||||
if (!resource.targetFront.isTopLevel) {
|
||||
// Only handle document events for the top level target.
|
||||
return;
|
||||
}
|
||||
|
||||
if (resource.name === "dom-loading") {
|
||||
// Netmonitor does not support dom-loading event yet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.actions) {
|
||||
this.actions.addTimingMarker(resource);
|
||||
}
|
||||
|
||||
if (resource.name === "dom-complete") {
|
||||
this.navigate();
|
||||
}
|
||||
|
||||
this.emitForTests(TEST_EVENTS.TIMELINE_EVENT, resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a HTTP request data payload
|
||||
*
|
||||
* @param {object} data data payload would like to sent to backend
|
||||
*/
|
||||
async sendHTTPRequest(data) {
|
||||
if (this.hasResourceWatcherSupport && this.currentTarget) {
|
||||
const networkContentFront = await this.currentTarget.getFront(
|
||||
"networkContent"
|
||||
);
|
||||
const { channelId } = await networkContentFront.sendHTTPRequest(data);
|
||||
return { channelId };
|
||||
}
|
||||
const {
|
||||
eventActor: { actor },
|
||||
} = await this.webConsoleFront.sendHTTPRequest(data);
|
||||
return { actor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Block future requests matching a filter.
|
||||
*
|
||||
* @param {object} filter request filter specifying what to block
|
||||
*/
|
||||
blockRequest(filter) {
|
||||
return this.webConsoleFront.blockRequest(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock future requests matching a filter.
|
||||
*
|
||||
* @param {object} filter request filter specifying what to unblock
|
||||
*/
|
||||
unblockRequest(filter) {
|
||||
return this.webConsoleFront.unblockRequest(filter);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the list of blocked URLs
|
||||
*/
|
||||
async getBlockedUrls() {
|
||||
if (this.hasResourceWatcherSupport && this.networkFront) {
|
||||
return this.networkFront.getBlockedUrls();
|
||||
}
|
||||
if (!this.webConsoleFront.traits.blockedUrls) {
|
||||
return [];
|
||||
}
|
||||
return this.webConsoleFront.getBlockedUrls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of blocked URLs
|
||||
*
|
||||
* @param {object} urls An array of URL strings
|
||||
*/
|
||||
async setBlockedUrls(urls) {
|
||||
if (this.hasResourceWatcherSupport && this.networkFront) {
|
||||
return this.networkFront.setBlockedUrls(urls);
|
||||
}
|
||||
return this.webConsoleFront.setBlockedUrls(urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set network preferences to control network flow
|
||||
*
|
||||
* @param {object} request request payload would like to sent to backend
|
||||
* @param {function} callback callback will be invoked after the request finished
|
||||
*/
|
||||
setPreferences(request) {
|
||||
return this.webConsoleFront.setPreferences(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a specific "activity" to be performed by the frontend.
|
||||
* This can be, for example, triggering reloads or enabling/disabling cache.
|
||||
*
|
||||
* @param {number} type The activity type. See the ACTIVITY_TYPE const.
|
||||
* @return {object} A promise resolved once the activity finishes and the frontend
|
||||
* is back into "standby" mode.
|
||||
*/
|
||||
triggerActivity(type) {
|
||||
// Puts the frontend into "standby" (when there's no particular activity).
|
||||
const standBy = () => {
|
||||
this.currentActivity = ACTIVITY_TYPE.NONE;
|
||||
};
|
||||
|
||||
// Waits for a series of "navigation start" and "navigation stop" events.
|
||||
const waitForNavigation = async () => {
|
||||
await this.currentTarget.once("will-navigate");
|
||||
await this.currentTarget.once("navigate");
|
||||
};
|
||||
|
||||
// Reconfigures the tab, optionally triggering a reload.
|
||||
const reconfigureTab = options => {
|
||||
return this.currentTarget.reconfigure({ options });
|
||||
};
|
||||
|
||||
// Reconfigures the tab and waits for the target to finish navigating.
|
||||
const reconfigureTabAndWaitForNavigation = options => {
|
||||
options.performReload = true;
|
||||
const navigationFinished = waitForNavigation();
|
||||
return reconfigureTab(options).then(() => navigationFinished);
|
||||
};
|
||||
switch (type) {
|
||||
case ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT:
|
||||
return reconfigureTabAndWaitForNavigation({}).then(standBy);
|
||||
case ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED:
|
||||
this.currentActivity = ACTIVITY_TYPE.ENABLE_CACHE;
|
||||
this.currentTarget.once("will-navigate", () => {
|
||||
this.currentActivity = type;
|
||||
});
|
||||
return reconfigureTabAndWaitForNavigation({
|
||||
cacheDisabled: false,
|
||||
performReload: true,
|
||||
}).then(standBy);
|
||||
case ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED:
|
||||
this.currentActivity = ACTIVITY_TYPE.DISABLE_CACHE;
|
||||
this.currentTarget.once("will-navigate", () => {
|
||||
this.currentActivity = type;
|
||||
});
|
||||
return reconfigureTabAndWaitForNavigation({
|
||||
cacheDisabled: true,
|
||||
performReload: true,
|
||||
}).then(standBy);
|
||||
case ACTIVITY_TYPE.ENABLE_CACHE:
|
||||
this.currentActivity = type;
|
||||
return reconfigureTab({
|
||||
cacheDisabled: false,
|
||||
performReload: false,
|
||||
}).then(standBy);
|
||||
case ACTIVITY_TYPE.DISABLE_CACHE:
|
||||
this.currentActivity = type;
|
||||
return reconfigureTab({
|
||||
cacheDisabled: true,
|
||||
performReload: false,
|
||||
}).then(standBy);
|
||||
}
|
||||
this.currentActivity = ACTIVITY_TYPE.NONE;
|
||||
return Promise.reject(new Error("Invalid activity type"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the full text of a LongString.
|
||||
*
|
||||
* @param {object|string} stringGrip
|
||||
* The long string grip containing the corresponding actor.
|
||||
* If you pass in a plain string (by accident or because you're lazy),
|
||||
* then a promise of the same string is simply returned.
|
||||
* @return {object}
|
||||
* A promise that is resolved when the full string contents
|
||||
* are available, or rejected if something goes wrong.
|
||||
*/
|
||||
getLongString(stringGrip) {
|
||||
return this.dataProvider.getLongString(stringGrip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter that access tab target instance.
|
||||
* @return {object} browser tab target instance
|
||||
*/
|
||||
getTabTarget() {
|
||||
return this.currentTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a given source in Debugger
|
||||
* @param {string} sourceURL source url
|
||||
* @param {number} sourceLine source line number
|
||||
*/
|
||||
viewSourceInDebugger(sourceURL, sourceLine, sourceColumn) {
|
||||
if (this.toolbox) {
|
||||
this.toolbox.viewSourceInDebugger(sourceURL, sourceLine, sourceColumn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch networkEventUpdate websocket message from back-end when
|
||||
* data provider is connected.
|
||||
* @param {object} request network request instance
|
||||
* @param {string} type NetworkEventUpdate type
|
||||
*/
|
||||
requestData(request, type) {
|
||||
return this.dataProvider.requestData(request, type);
|
||||
}
|
||||
|
||||
getTimingMarker(name) {
|
||||
if (!this.getState) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const state = this.getState();
|
||||
return getDisplayedTimingMarker(state, name);
|
||||
}
|
||||
|
||||
async updateNetworkThrottling(enabled, profile) {
|
||||
const throttlingFront =
|
||||
this.hasResourceWatcherSupport && this.networkFront
|
||||
? this.networkFront
|
||||
: this.responsiveFront;
|
||||
|
||||
if (!enabled) {
|
||||
throttlingFront.clearNetworkThrottling();
|
||||
} else {
|
||||
// The profile can be either a profile id which is used to
|
||||
// search the predefined throttle profiles or a profile object
|
||||
// as defined in the trottle tests.
|
||||
if (typeof profile === "string") {
|
||||
profile = throttlingProfiles.find(({ id }) => id == profile);
|
||||
}
|
||||
const { download, upload, latency } = profile;
|
||||
await throttlingFront.setNetworkThrottling({
|
||||
downloadThroughput: download,
|
||||
uploadThroughput: upload,
|
||||
latency,
|
||||
});
|
||||
}
|
||||
|
||||
this.emitForTests(TEST_EVENTS.THROTTLING_CHANGED, { profile });
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire events for the owner object. These events are only
|
||||
* used in tests so, don't fire them in production release.
|
||||
*/
|
||||
emitForTests(type, data) {
|
||||
if (this.owner) {
|
||||
this.owner.emitForTests(type, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FirefoxConnector;
|
|
@ -4,114 +4,614 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const {
|
||||
ACTIVITY_TYPE,
|
||||
EVENTS,
|
||||
TEST_EVENTS,
|
||||
} = require("devtools/client/netmonitor/src/constants");
|
||||
const FirefoxDataProvider = require("devtools/client/netmonitor/src/connector/firefox-data-provider");
|
||||
const {
|
||||
getDisplayedTimingMarker,
|
||||
} = require("devtools/client/netmonitor/src/selectors/index");
|
||||
|
||||
// Network throttling
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"throttlingProfiles",
|
||||
"devtools/client/shared/components/throttling/profiles"
|
||||
);
|
||||
|
||||
/**
|
||||
* Generic connector wrapper object that is responsible for
|
||||
* instantiating specific connector implementation according
|
||||
* to the client type.
|
||||
* Connector to Firefox backend.
|
||||
*/
|
||||
class Connector {
|
||||
constructor() {
|
||||
this.connector = null;
|
||||
|
||||
// Bind public API
|
||||
// Public methods
|
||||
this.connect = this.connect.bind(this);
|
||||
this.disconnect = this.disconnect.bind(this);
|
||||
this.connectFirefox = this.connectFirefox.bind(this);
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.getTabTarget = this.getTabTarget.bind(this);
|
||||
this.willNavigate = this.willNavigate.bind(this);
|
||||
this.navigate = this.navigate.bind(this);
|
||||
this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
|
||||
this.setPreferences = this.setPreferences.bind(this);
|
||||
this.triggerActivity = this.triggerActivity.bind(this);
|
||||
this.getTabTarget = this.getTabTarget.bind(this);
|
||||
this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
|
||||
this.requestData = this.requestData.bind(this);
|
||||
this.getTimingMarker = this.getTimingMarker.bind(this);
|
||||
this.updateNetworkThrottling = this.updateNetworkThrottling.bind(this);
|
||||
|
||||
// Internals
|
||||
this.getLongString = this.getLongString.bind(this);
|
||||
this.onTargetAvailable = this.onTargetAvailable.bind(this);
|
||||
this.onResourceAvailable = this.onResourceAvailable.bind(this);
|
||||
this.onResourceUpdated = this.onResourceUpdated.bind(this);
|
||||
|
||||
this.networkFront = null;
|
||||
this.listenForNetworkEvents = true;
|
||||
}
|
||||
|
||||
// Connect/Disconnect API
|
||||
get currentTarget() {
|
||||
return this.toolbox.targetList.targetFront;
|
||||
}
|
||||
|
||||
get hasResourceWatcherSupport() {
|
||||
return this.toolbox.resourceWatcher.hasResourceWatcherSupport(
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT
|
||||
);
|
||||
}
|
||||
|
||||
get watcherFront() {
|
||||
return this.toolbox.resourceWatcher.watcherFront;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the backend.
|
||||
*
|
||||
* @param {Object} connection object with e.g. reference to the Toolbox.
|
||||
* @param {Object} actions (optional) is used to fire Redux actions to update store.
|
||||
* @param {Object} getState (optional) is used to get access to the state.
|
||||
*/
|
||||
async connect(connection, actions, getState) {
|
||||
if (!connection || !connection.tab) {
|
||||
return;
|
||||
}
|
||||
this.actions = actions;
|
||||
this.getState = getState;
|
||||
this.toolbox = connection.toolbox;
|
||||
|
||||
const { clientType } = connection.tab;
|
||||
switch (clientType) {
|
||||
case "firefox":
|
||||
await this.connectFirefox(connection, actions, getState);
|
||||
break;
|
||||
default:
|
||||
throw Error(`Unknown client type - ${clientType}`);
|
||||
}
|
||||
// The owner object (NetMonitorAPI) received all events.
|
||||
this.owner = connection.owner;
|
||||
|
||||
await this.toolbox.targetList.watchTargets(
|
||||
[this.toolbox.targetList.TYPES.FRAME],
|
||||
this.onTargetAvailable
|
||||
);
|
||||
|
||||
await this.toolbox.resourceWatcher.watchResources(
|
||||
[this.toolbox.resourceWatcher.TYPES.DOCUMENT_EVENT],
|
||||
{ onAvailable: this.onResourceAvailable }
|
||||
);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.connector && this.connector.disconnect();
|
||||
// As this function might be called twice, we need to guard if already called.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
connectFirefox(connection, actions, getState) {
|
||||
const FirefoxConnector = require("devtools/client/netmonitor/src/connector/firefox-connector");
|
||||
this.connector = new FirefoxConnector();
|
||||
return this.connector.connect(connection, actions, getState);
|
||||
this._destroyed = true;
|
||||
|
||||
this.toolbox.targetList.unwatchTargets(
|
||||
[this.toolbox.targetList.TYPES.FRAME],
|
||||
this.onTargetAvailable
|
||||
);
|
||||
|
||||
this.toolbox.resourceWatcher.unwatchResources(
|
||||
[this.toolbox.resourceWatcher.TYPES.DOCUMENT_EVENT],
|
||||
{ onAvailable: this.onResourceAvailable }
|
||||
);
|
||||
|
||||
if (this.actions) {
|
||||
this.actions.batchReset();
|
||||
}
|
||||
|
||||
pause() {
|
||||
return this.connector.pause();
|
||||
this.removeListeners();
|
||||
|
||||
this.currentTarget.off("will-navigate", this.willNavigate);
|
||||
|
||||
this.webConsoleFront = null;
|
||||
this.dataProvider = null;
|
||||
}
|
||||
|
||||
resume() {
|
||||
return this.connector.resume();
|
||||
async pause() {
|
||||
this.listenForNetworkEvents = false;
|
||||
}
|
||||
|
||||
enableActions() {
|
||||
this.connector.enableActions(...arguments);
|
||||
async resume() {
|
||||
this.listenForNetworkEvents = true;
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
getLongString() {
|
||||
return this.connector.getLongString(...arguments);
|
||||
async onTargetAvailable({ targetFront, isTargetSwitching }) {
|
||||
if (!targetFront.isTopLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTargetSwitching) {
|
||||
this.willNavigate();
|
||||
}
|
||||
|
||||
// Listener for `will-navigate` event is (un)registered outside
|
||||
// of the `addListeners` and `removeListeners` methods since
|
||||
// these are used to pause/resume the connector.
|
||||
// Paused network panel should be automatically resumed when page
|
||||
// reload, so `will-navigate` listener needs to be there all the time.
|
||||
targetFront.on("will-navigate", this.willNavigate);
|
||||
|
||||
this.webConsoleFront = await this.currentTarget.getFront("console");
|
||||
|
||||
this.dataProvider = new FirefoxDataProvider({
|
||||
webConsoleFront: this.webConsoleFront,
|
||||
actions: this.actions,
|
||||
owner: this.owner,
|
||||
resourceWatcher: this.toolbox.resourceWatcher,
|
||||
});
|
||||
|
||||
// If this is the first top level target, lets register all the listeners
|
||||
if (!isTargetSwitching) {
|
||||
await this.addListeners();
|
||||
}
|
||||
|
||||
// Initialize Responsive Emulation front for network throttling.
|
||||
this.responsiveFront = await this.currentTarget.getFront("responsive");
|
||||
if (this.hasResourceWatcherSupport) {
|
||||
this.networkFront = await this.watcherFront.getNetworkParentActor();
|
||||
}
|
||||
}
|
||||
|
||||
async onResourceAvailable(resources) {
|
||||
for (const resource of resources) {
|
||||
const { TYPES } = this.toolbox.resourceWatcher;
|
||||
|
||||
if (resource.resourceType === TYPES.DOCUMENT_EVENT) {
|
||||
this.onDocEvent(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.listenForNetworkEvents) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.NETWORK_EVENT) {
|
||||
this.dataProvider.onNetworkResourceAvailable(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.NETWORK_EVENT_STACKTRACE) {
|
||||
this.dataProvider.onStackTraceAvailable(resource);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.WEBSOCKET) {
|
||||
const { wsMessageType } = resource;
|
||||
|
||||
switch (wsMessageType) {
|
||||
case "webSocketOpened": {
|
||||
this.dataProvider.onWebSocketOpened(
|
||||
resource.httpChannelId,
|
||||
resource.effectiveURI,
|
||||
resource.protocols,
|
||||
resource.extensions
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "webSocketClosed": {
|
||||
this.dataProvider.onWebSocketClosed(
|
||||
resource.httpChannelId,
|
||||
resource.wasClean,
|
||||
resource.code,
|
||||
resource.reason
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "frameReceived": {
|
||||
this.dataProvider.onFrameReceived(
|
||||
resource.httpChannelId,
|
||||
resource.data
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "frameSent": {
|
||||
this.dataProvider.onFrameSent(
|
||||
resource.httpChannelId,
|
||||
resource.data
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (resource.resourceType === TYPES.SERVER_SENT_EVENT) {
|
||||
const { messageType, httpChannelId, data } = resource;
|
||||
switch (messageType) {
|
||||
case "eventSourceConnectionClosed": {
|
||||
this.dataProvider.onEventSourceConnectionClosed(httpChannelId);
|
||||
break;
|
||||
}
|
||||
case "eventReceived": {
|
||||
this.dataProvider.onEventReceived(httpChannelId, data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onResourceUpdated(updates) {
|
||||
for (const { resource, update } of updates) {
|
||||
if (
|
||||
resource.resourceType ===
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT &&
|
||||
this.listenForNetworkEvents
|
||||
) {
|
||||
this.dataProvider.onNetworkResourceUpdated(resource, update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async addListeners(ignoreExistingResources = false) {
|
||||
const targetResources = [
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT,
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT_STACKTRACE,
|
||||
];
|
||||
if (Services.prefs.getBoolPref("devtools.netmonitor.features.webSockets")) {
|
||||
targetResources.push(this.toolbox.resourceWatcher.TYPES.WEBSOCKET);
|
||||
}
|
||||
|
||||
if (
|
||||
Services.prefs.getBoolPref(
|
||||
"devtools.netmonitor.features.serverSentEvents"
|
||||
)
|
||||
) {
|
||||
targetResources.push(
|
||||
this.toolbox.resourceWatcher.TYPES.SERVER_SENT_EVENT
|
||||
);
|
||||
}
|
||||
|
||||
await this.toolbox.resourceWatcher.watchResources(targetResources, {
|
||||
onAvailable: this.onResourceAvailable,
|
||||
onUpdated: this.onResourceUpdated,
|
||||
ignoreExistingResources,
|
||||
});
|
||||
}
|
||||
|
||||
removeListeners() {
|
||||
this.toolbox.resourceWatcher.unwatchResources(
|
||||
[
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT,
|
||||
this.toolbox.resourceWatcher.TYPES.NETWORK_EVENT_STACKTRACE,
|
||||
this.toolbox.resourceWatcher.TYPES.WEBSOCKET,
|
||||
this.toolbox.resourceWatcher.TYPES.SERVER_SENT_EVENT,
|
||||
],
|
||||
{
|
||||
onAvailable: this.onResourceAvailable,
|
||||
onUpdated: this.onResourceUpdated,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
enableActions(enable) {
|
||||
this.dataProvider.enableActions(enable);
|
||||
}
|
||||
|
||||
willNavigate() {
|
||||
if (this.actions) {
|
||||
if (!Services.prefs.getBoolPref("devtools.netmonitor.persistlog")) {
|
||||
this.actions.batchReset();
|
||||
this.actions.clearRequests();
|
||||
} else {
|
||||
// If the log is persistent, just clear all accumulated timing markers.
|
||||
this.actions.clearTimingMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.actions && this.getState) {
|
||||
const state = this.getState();
|
||||
// Resume is done automatically on page reload/navigation.
|
||||
if (!state.requests.recording) {
|
||||
this.actions.toggleRecording();
|
||||
}
|
||||
|
||||
// Stop any ongoing search.
|
||||
if (state.search.ongoingSearch) {
|
||||
this.actions.stopOngoingSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
navigate() {
|
||||
if (!this.dataProvider.hasPendingRequests()) {
|
||||
this.onReloaded();
|
||||
return;
|
||||
}
|
||||
const listener = () => {
|
||||
if (this.dataProvider && this.dataProvider.hasPendingRequests()) {
|
||||
return;
|
||||
}
|
||||
if (this.owner) {
|
||||
this.owner.off(EVENTS.PAYLOAD_READY, listener);
|
||||
}
|
||||
// Netmonitor may already be destroyed,
|
||||
// so do not try to notify the listeners
|
||||
if (this.dataProvider) {
|
||||
this.onReloaded();
|
||||
}
|
||||
};
|
||||
if (this.owner) {
|
||||
this.owner.on(EVENTS.PAYLOAD_READY, listener);
|
||||
}
|
||||
}
|
||||
|
||||
onReloaded() {
|
||||
const panel = this.toolbox.getPanel("netmonitor");
|
||||
if (panel) {
|
||||
panel.emit("reloaded");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The "DOMContentLoaded" and "Load" events sent by the console actor.
|
||||
*
|
||||
* @param {object} resource The DOCUMENT_EVENT resource
|
||||
*/
|
||||
onDocEvent(resource) {
|
||||
if (!resource.targetFront.isTopLevel) {
|
||||
// Only handle document events for the top level target.
|
||||
return;
|
||||
}
|
||||
|
||||
if (resource.name === "dom-loading") {
|
||||
// Netmonitor does not support dom-loading event yet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.actions) {
|
||||
this.actions.addTimingMarker(resource);
|
||||
}
|
||||
|
||||
if (resource.name === "dom-complete") {
|
||||
this.navigate();
|
||||
}
|
||||
|
||||
this.emitForTests(TEST_EVENTS.TIMELINE_EVENT, resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a HTTP request data payload
|
||||
*
|
||||
* @param {object} data data payload would like to sent to backend
|
||||
*/
|
||||
async sendHTTPRequest(data) {
|
||||
if (this.hasResourceWatcherSupport && this.currentTarget) {
|
||||
const networkContentFront = await this.currentTarget.getFront(
|
||||
"networkContent"
|
||||
);
|
||||
const { channelId } = await networkContentFront.sendHTTPRequest(data);
|
||||
return { channelId };
|
||||
}
|
||||
const {
|
||||
eventActor: { actor },
|
||||
} = await this.webConsoleFront.sendHTTPRequest(data);
|
||||
return { actor };
|
||||
}
|
||||
|
||||
/**
|
||||
* Block future requests matching a filter.
|
||||
*
|
||||
* @param {object} filter request filter specifying what to block
|
||||
*/
|
||||
blockRequest(filter) {
|
||||
return this.webConsoleFront.blockRequest(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock future requests matching a filter.
|
||||
*
|
||||
* @param {object} filter request filter specifying what to unblock
|
||||
*/
|
||||
unblockRequest(filter) {
|
||||
return this.webConsoleFront.unblockRequest(filter);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the list of blocked URLs
|
||||
*/
|
||||
async getBlockedUrls() {
|
||||
if (this.hasResourceWatcherSupport && this.networkFront) {
|
||||
return this.networkFront.getBlockedUrls();
|
||||
}
|
||||
if (!this.webConsoleFront.traits.blockedUrls) {
|
||||
return [];
|
||||
}
|
||||
return this.webConsoleFront.getBlockedUrls();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the list of blocked URLs
|
||||
*
|
||||
* @param {object} urls An array of URL strings
|
||||
*/
|
||||
async setBlockedUrls(urls) {
|
||||
if (this.hasResourceWatcherSupport && this.networkFront) {
|
||||
return this.networkFront.setBlockedUrls(urls);
|
||||
}
|
||||
return this.webConsoleFront.setBlockedUrls(urls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set network preferences to control network flow
|
||||
*
|
||||
* @param {object} request request payload would like to sent to backend
|
||||
* @param {function} callback callback will be invoked after the request finished
|
||||
*/
|
||||
setPreferences(request) {
|
||||
return this.webConsoleFront.setPreferences(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a specific "activity" to be performed by the frontend.
|
||||
* This can be, for example, triggering reloads or enabling/disabling cache.
|
||||
*
|
||||
* @param {number} type The activity type. See the ACTIVITY_TYPE const.
|
||||
* @return {object} A promise resolved once the activity finishes and the frontend
|
||||
* is back into "standby" mode.
|
||||
*/
|
||||
triggerActivity(type) {
|
||||
// Puts the frontend into "standby" (when there's no particular activity).
|
||||
const standBy = () => {
|
||||
this.currentActivity = ACTIVITY_TYPE.NONE;
|
||||
};
|
||||
|
||||
// Waits for a series of "navigation start" and "navigation stop" events.
|
||||
const waitForNavigation = async () => {
|
||||
await this.currentTarget.once("will-navigate");
|
||||
await this.currentTarget.once("navigate");
|
||||
};
|
||||
|
||||
// Reconfigures the tab, optionally triggering a reload.
|
||||
const reconfigureTab = options => {
|
||||
return this.currentTarget.reconfigure({ options });
|
||||
};
|
||||
|
||||
// Reconfigures the tab and waits for the target to finish navigating.
|
||||
const reconfigureTabAndWaitForNavigation = options => {
|
||||
options.performReload = true;
|
||||
const navigationFinished = waitForNavigation();
|
||||
return reconfigureTab(options).then(() => navigationFinished);
|
||||
};
|
||||
switch (type) {
|
||||
case ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT:
|
||||
return reconfigureTabAndWaitForNavigation({}).then(standBy);
|
||||
case ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED:
|
||||
this.currentActivity = ACTIVITY_TYPE.ENABLE_CACHE;
|
||||
this.currentTarget.once("will-navigate", () => {
|
||||
this.currentActivity = type;
|
||||
});
|
||||
return reconfigureTabAndWaitForNavigation({
|
||||
cacheDisabled: false,
|
||||
performReload: true,
|
||||
}).then(standBy);
|
||||
case ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED:
|
||||
this.currentActivity = ACTIVITY_TYPE.DISABLE_CACHE;
|
||||
this.currentTarget.once("will-navigate", () => {
|
||||
this.currentActivity = type;
|
||||
});
|
||||
return reconfigureTabAndWaitForNavigation({
|
||||
cacheDisabled: true,
|
||||
performReload: true,
|
||||
}).then(standBy);
|
||||
case ACTIVITY_TYPE.ENABLE_CACHE:
|
||||
this.currentActivity = type;
|
||||
return reconfigureTab({
|
||||
cacheDisabled: false,
|
||||
performReload: false,
|
||||
}).then(standBy);
|
||||
case ACTIVITY_TYPE.DISABLE_CACHE:
|
||||
this.currentActivity = type;
|
||||
return reconfigureTab({
|
||||
cacheDisabled: true,
|
||||
performReload: false,
|
||||
}).then(standBy);
|
||||
}
|
||||
this.currentActivity = ACTIVITY_TYPE.NONE;
|
||||
return Promise.reject(new Error("Invalid activity type"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the full text of a LongString.
|
||||
*
|
||||
* @param {object|string} stringGrip
|
||||
* The long string grip containing the corresponding actor.
|
||||
* If you pass in a plain string (by accident or because you're lazy),
|
||||
* then a promise of the same string is simply returned.
|
||||
* @return {object}
|
||||
* A promise that is resolved when the full string contents
|
||||
* are available, or rejected if something goes wrong.
|
||||
*/
|
||||
getLongString(stringGrip) {
|
||||
return this.dataProvider.getLongString(stringGrip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter that access tab target instance.
|
||||
* @return {object} browser tab target instance
|
||||
*/
|
||||
getTabTarget() {
|
||||
return this.connector.getTabTarget();
|
||||
return this.currentTarget;
|
||||
}
|
||||
|
||||
sendHTTPRequest() {
|
||||
return this.connector.sendHTTPRequest(...arguments);
|
||||
/**
|
||||
* Open a given source in Debugger
|
||||
* @param {string} sourceURL source url
|
||||
* @param {number} sourceLine source line number
|
||||
*/
|
||||
viewSourceInDebugger(sourceURL, sourceLine, sourceColumn) {
|
||||
if (this.toolbox) {
|
||||
this.toolbox.viewSourceInDebugger(sourceURL, sourceLine, sourceColumn);
|
||||
}
|
||||
}
|
||||
|
||||
getBlockedUrls() {
|
||||
return this.connector.getBlockedUrls();
|
||||
/**
|
||||
* Fetch networkEventUpdate websocket message from back-end when
|
||||
* data provider is connected.
|
||||
* @param {object} request network request instance
|
||||
* @param {string} type NetworkEventUpdate type
|
||||
*/
|
||||
requestData(request, type) {
|
||||
return this.dataProvider.requestData(request, type);
|
||||
}
|
||||
|
||||
setBlockedUrls() {
|
||||
return this.connector.setBlockedUrls(...arguments);
|
||||
getTimingMarker(name) {
|
||||
if (!this.getState) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
setPreferences() {
|
||||
return this.connector.setPreferences(...arguments);
|
||||
const state = this.getState();
|
||||
return getDisplayedTimingMarker(state, name);
|
||||
}
|
||||
|
||||
triggerActivity() {
|
||||
return this.connector.triggerActivity(...arguments);
|
||||
async updateNetworkThrottling(enabled, profile) {
|
||||
const throttlingFront =
|
||||
this.hasResourceWatcherSupport && this.networkFront
|
||||
? this.networkFront
|
||||
: this.responsiveFront;
|
||||
|
||||
if (!enabled) {
|
||||
throttlingFront.clearNetworkThrottling();
|
||||
} else {
|
||||
// The profile can be either a profile id which is used to
|
||||
// search the predefined throttle profiles or a profile object
|
||||
// as defined in the trottle tests.
|
||||
if (typeof profile === "string") {
|
||||
profile = throttlingProfiles.find(({ id }) => id == profile);
|
||||
}
|
||||
const { download, upload, latency } = profile;
|
||||
await throttlingFront.setNetworkThrottling({
|
||||
downloadThroughput: download,
|
||||
uploadThroughput: upload,
|
||||
latency,
|
||||
});
|
||||
}
|
||||
|
||||
viewSourceInDebugger() {
|
||||
return this.connector.viewSourceInDebugger(...arguments);
|
||||
this.emitForTests(TEST_EVENTS.THROTTLING_CHANGED, { profile });
|
||||
}
|
||||
|
||||
requestData() {
|
||||
return this.connector.requestData(...arguments);
|
||||
/**
|
||||
* Fire events for the owner object. These events are only
|
||||
* used in tests so, don't fire them in production release.
|
||||
*/
|
||||
emitForTests(type, data) {
|
||||
if (this.owner) {
|
||||
this.owner.emitForTests(type, data);
|
||||
}
|
||||
|
||||
getTimingMarker() {
|
||||
return this.connector.getTimingMarker(...arguments);
|
||||
}
|
||||
|
||||
updateNetworkThrottling() {
|
||||
return this.connector.updateNetworkThrottling(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Connector = Connector;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
"firefox-connector.js",
|
||||
"firefox-data-provider.js",
|
||||
"index.js",
|
||||
)
|
||||
|
|
|
@ -33,7 +33,7 @@ executed by the page (including inner iframes). The final export is triggered
|
|||
by HarAutomation at the right time.
|
||||
|
||||
Note: this object is using it's own logic to fetch data from the backend.
|
||||
It should reuse the Firefox Connector (src/connector/firefox-connector),
|
||||
It should reuse the Netmonitor Connector (src/connector/index),
|
||||
so we don't have to maintain two code paths.
|
||||
|
||||
## HarExporter
|
||||
|
|
Загрузка…
Ссылка в новой задаче