browser(firefox): use browsingContextID for frame IDs (#3999)
BrowsingContextIDs are consistent across the processes, so we can use them to target frames in both browser and content processes. This will aid browser-side navigation. As a nice side-effect, we can drop a round-trip to the content process for every `requestWillBeSent` event since we *almost* always can attribute all network events to the proper parent frames. I say "almost", because we in fact **fail** to correctly attribute requests from workers that are instantiated by subframes. This, however, is not working in Chromium ATM, so I consider this to be a minor regression that is worth the simplification.
This commit is contained in:
parent
b3497b333e
commit
2631e1a809
|
|
@ -1,2 +1,2 @@
|
||||||
1173
|
1174
|
||||||
Changed: yurys@chromium.org Thu Sep 10 12:35:25 PDT 2020
|
Changed: lushnikov@chromium.org Tue Sep 29 02:02:37 MDT 2020
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerat
|
||||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
class Helper {
|
class Helper {
|
||||||
|
|
||||||
addObserver(handler, topic) {
|
addObserver(handler, topic) {
|
||||||
Services.obs.addObserver(handler, topic);
|
Services.obs.addObserver(handler, topic);
|
||||||
return () => Services.obs.removeObserver(handler, topic);
|
return () => Services.obs.removeObserver(handler, topic);
|
||||||
|
|
@ -112,6 +113,12 @@ class Helper {
|
||||||
}
|
}
|
||||||
return '<unknown error>';
|
return '<unknown error>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
browsingContextToFrameId(browsingContext) {
|
||||||
|
if (!browsingContext)
|
||||||
|
return undefined;
|
||||||
|
return 'frame-' + browsingContext.id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = [ "Helper" ];
|
var EXPORTED_SYMBOLS = [ "Helper" ];
|
||||||
|
|
|
||||||
|
|
@ -489,10 +489,24 @@ class NetworkRequest {
|
||||||
const pageNetwork = this._activePageNetwork();
|
const pageNetwork = this._activePageNetwork();
|
||||||
if (!pageNetwork)
|
if (!pageNetwork)
|
||||||
return;
|
return;
|
||||||
const causeType = this.httpChannel.loadInfo ? this.httpChannel.loadInfo.externalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER;
|
const loadInfo = this.httpChannel.loadInfo;
|
||||||
const internalCauseType = this.httpChannel.loadInfo ? this.httpChannel.loadInfo.internalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER;
|
const causeType = loadInfo?.externalContentPolicyType || Ci.nsIContentPolicy.TYPE_OTHER;
|
||||||
|
const internalCauseType = loadInfo?.internalContentPolicyType || Ci.nsIContentPolicy.TYPE_OTHER;
|
||||||
|
|
||||||
|
let browsingContext = loadInfo?.frameBrowsingContext || loadInfo?.browsingContext;
|
||||||
|
// TODO: Unfortunately, requests from web workers don't have frameBrowsingContext or
|
||||||
|
// browsingContext.
|
||||||
|
//
|
||||||
|
// We fail to attribute them to the original frames on the browser side, but we
|
||||||
|
// can use load context top frame to attribute them to the top frame at least.
|
||||||
|
if (!browsingContext) {
|
||||||
|
const loadContext = helper.getLoadContext(this.httpChannel);
|
||||||
|
browsingContext = loadContext?.topFrameElement?.browsingContext;
|
||||||
|
}
|
||||||
|
|
||||||
pageNetwork.emit(PageNetwork.Events.Request, {
|
pageNetwork.emit(PageNetwork.Events.Request, {
|
||||||
url: this.httpChannel.URI.spec,
|
url: this.httpChannel.URI.spec,
|
||||||
|
frameId: helper.browsingContextToFrameId(browsingContext),
|
||||||
isIntercepted,
|
isIntercepted,
|
||||||
requestId: this.requestId,
|
requestId: this.requestId,
|
||||||
redirectedFrom: this.redirectedFromId,
|
redirectedFrom: this.redirectedFromId,
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ class Frame {
|
||||||
this._runtime = runtime;
|
this._runtime = runtime;
|
||||||
this._docShell = docShell;
|
this._docShell = docShell;
|
||||||
this._children = new Set();
|
this._children = new Set();
|
||||||
this._frameId = helper.generateId();
|
this._frameId = helper.browsingContextToFrameId(this._docShell.browsingContext);
|
||||||
this._parentFrame = null;
|
this._parentFrame = null;
|
||||||
this._url = '';
|
this._url = '';
|
||||||
if (docShell.domWindow && docShell.domWindow.location)
|
if (docShell.domWindow && docShell.domWindow.location)
|
||||||
|
|
|
||||||
|
|
@ -1,61 +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 Ci = Components.interfaces;
|
|
||||||
const Cr = Components.results;
|
|
||||||
const Cu = Components.utils;
|
|
||||||
|
|
||||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
|
||||||
|
|
||||||
const helper = new Helper();
|
|
||||||
|
|
||||||
class NetworkMonitor {
|
|
||||||
constructor(rootDocShell, frameTree) {
|
|
||||||
this._frameTree = frameTree;
|
|
||||||
this._requestDetails = new Map();
|
|
||||||
|
|
||||||
this._eventListeners = [
|
|
||||||
helper.addObserver(this._onRequest.bind(this), 'http-on-opening-request'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
_onRequest(channel) {
|
|
||||||
if (!(channel instanceof Ci.nsIHttpChannel))
|
|
||||||
return;
|
|
||||||
const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
|
|
||||||
const loadContext = helper.getLoadContext(httpChannel);
|
|
||||||
if (!loadContext)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
const window = loadContext.associatedWindow;
|
|
||||||
const frame = this._frameTree.frameForDocShell(window.docShell);
|
|
||||||
if (!frame)
|
|
||||||
return;
|
|
||||||
const typeId = httpChannel.loadInfo ? httpChannel.loadInfo.internalContentPolicyType : Ci.nsIContentPolicy.TYPE_OTHER;
|
|
||||||
// Channel ids are not unique. We combine them with the typeId
|
|
||||||
// to better distinguish requests. For example, favicon requests
|
|
||||||
// have the same channel id as their associated document request.
|
|
||||||
const channelKey = httpChannel.channelId + ':' + typeId;
|
|
||||||
this._requestDetails.set(channelKey, {
|
|
||||||
frameId: frame.id(),
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// Accessing loadContext.associatedWindow sometimes throws.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDetails(channelKey) {
|
|
||||||
return this._requestDetails.get(channelKey) || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this._requestDetails.clear();
|
|
||||||
helper.removeListeners(this._eventListeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ['NetworkMonitor'];
|
|
||||||
this.NetworkMonitor = NetworkMonitor;
|
|
||||||
|
|
||||||
|
|
@ -112,7 +112,7 @@ class FrameData {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PageAgent {
|
class PageAgent {
|
||||||
constructor(messageManager, browserChannel, sessionId, frameTree, networkMonitor) {
|
constructor(messageManager, browserChannel, sessionId, frameTree) {
|
||||||
this._messageManager = messageManager;
|
this._messageManager = messageManager;
|
||||||
this._browserChannel = browserChannel;
|
this._browserChannel = browserChannel;
|
||||||
this._sessionId = sessionId;
|
this._sessionId = sessionId;
|
||||||
|
|
@ -120,7 +120,6 @@ class PageAgent {
|
||||||
this._browserRuntime = browserChannel.connect(sessionId + 'runtime');
|
this._browserRuntime = browserChannel.connect(sessionId + 'runtime');
|
||||||
this._frameTree = frameTree;
|
this._frameTree = frameTree;
|
||||||
this._runtime = frameTree.runtime();
|
this._runtime = frameTree.runtime();
|
||||||
this._networkMonitor = networkMonitor;
|
|
||||||
|
|
||||||
this._frameData = new Map();
|
this._frameData = new Map();
|
||||||
this._workerData = new Map();
|
this._workerData = new Map();
|
||||||
|
|
@ -146,7 +145,6 @@ class PageAgent {
|
||||||
navigate: this._navigate.bind(this),
|
navigate: this._navigate.bind(this),
|
||||||
reload: this._reload.bind(this),
|
reload: this._reload.bind(this),
|
||||||
removeScriptToEvaluateOnNewDocument: this._removeScriptToEvaluateOnNewDocument.bind(this),
|
removeScriptToEvaluateOnNewDocument: this._removeScriptToEvaluateOnNewDocument.bind(this),
|
||||||
requestDetails: this._requestDetails.bind(this),
|
|
||||||
screenshot: this._screenshot.bind(this),
|
screenshot: this._screenshot.bind(this),
|
||||||
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
|
scrollIntoViewIfNeeded: this._scrollIntoViewIfNeeded.bind(this),
|
||||||
setCacheDisabled: this._setCacheDisabled.bind(this),
|
setCacheDisabled: this._setCacheDisabled.bind(this),
|
||||||
|
|
@ -170,10 +168,6 @@ class PageAgent {
|
||||||
this._dataTransfer = null;
|
this._dataTransfer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_requestDetails({channelKey}) {
|
|
||||||
return this._networkMonitor.requestDetails(channelKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _setEmulatedMedia({type, colorScheme}) {
|
async _setEmulatedMedia({type, colorScheme}) {
|
||||||
const docShell = this._frameTree.mainFrame().docShell();
|
const docShell = this._frameTree.mainFrame().docShell();
|
||||||
const cv = docShell.contentViewer;
|
const cv = docShell.contentViewer;
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,17 @@
|
||||||
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
|
||||||
const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js');
|
const {FrameTree} = ChromeUtils.import('chrome://juggler/content/content/FrameTree.js');
|
||||||
const {NetworkMonitor} = ChromeUtils.import('chrome://juggler/content/content/NetworkMonitor.js');
|
|
||||||
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
|
||||||
const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
|
const {PageAgent} = ChromeUtils.import('chrome://juggler/content/content/PageAgent.js');
|
||||||
|
|
||||||
let frameTree;
|
let frameTree;
|
||||||
let networkMonitor;
|
|
||||||
const helper = new Helper();
|
const helper = new Helper();
|
||||||
const messageManager = this;
|
const messageManager = this;
|
||||||
|
|
||||||
const sessions = new Map();
|
const sessions = new Map();
|
||||||
|
|
||||||
function createContentSession(channel, sessionId) {
|
function createContentSession(channel, sessionId) {
|
||||||
const pageAgent = new PageAgent(messageManager, channel, sessionId, frameTree, networkMonitor);
|
const pageAgent = new PageAgent(messageManager, channel, sessionId, frameTree);
|
||||||
sessions.set(sessionId, [pageAgent]);
|
sessions.set(sessionId, [pageAgent]);
|
||||||
pageAgent.enable();
|
pageAgent.enable();
|
||||||
}
|
}
|
||||||
|
|
@ -118,7 +116,6 @@ function initialize() {
|
||||||
frameTree.addScriptToEvaluateOnNewDocument(script);
|
frameTree.addScriptToEvaluateOnNewDocument(script);
|
||||||
for (const { name, script } of bindings)
|
for (const { name, script } of bindings)
|
||||||
frameTree.addBinding(name, script);
|
frameTree.addBinding(name, script);
|
||||||
networkMonitor = new NetworkMonitor(docShell, frameTree);
|
|
||||||
|
|
||||||
const channel = SimpleChannel.createForMessageManager('content::page', messageManager);
|
const channel = SimpleChannel.createForMessageManager('content::page', messageManager);
|
||||||
|
|
||||||
|
|
@ -180,7 +177,6 @@ function initialize() {
|
||||||
for (const sessionId of sessions.keys())
|
for (const sessionId of sessions.keys())
|
||||||
disposeContentSession(sessionId);
|
disposeContentSession(sessionId);
|
||||||
|
|
||||||
networkMonitor.dispose();
|
|
||||||
frameTree.dispose();
|
frameTree.dispose();
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ juggler.jar:
|
||||||
content/protocol/AccessibilityHandler.js (protocol/AccessibilityHandler.js)
|
content/protocol/AccessibilityHandler.js (protocol/AccessibilityHandler.js)
|
||||||
content/content/main.js (content/main.js)
|
content/content/main.js (content/main.js)
|
||||||
content/content/FrameTree.js (content/FrameTree.js)
|
content/content/FrameTree.js (content/FrameTree.js)
|
||||||
content/content/NetworkMonitor.js (content/NetworkMonitor.js)
|
|
||||||
content/content/PageAgent.js (content/PageAgent.js)
|
content/content/PageAgent.js (content/PageAgent.js)
|
||||||
content/content/Runtime.js (content/Runtime.js)
|
content/content/Runtime.js (content/Runtime.js)
|
||||||
content/content/WorkerMain.js (content/WorkerMain.js)
|
content/content/WorkerMain.js (content/WorkerMain.js)
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,9 @@ const helper = new Helper();
|
||||||
class NetworkHandler {
|
class NetworkHandler {
|
||||||
constructor(target, session, contentChannel) {
|
constructor(target, session, contentChannel) {
|
||||||
this._session = session;
|
this._session = session;
|
||||||
this._contentPage = contentChannel.connect(session.sessionId() + 'page');
|
|
||||||
this._httpActivity = new Map();
|
|
||||||
this._enabled = false;
|
this._enabled = false;
|
||||||
this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target);
|
this._pageNetwork = NetworkObserver.instance().pageNetworkForTarget(target);
|
||||||
this._requestInterception = false;
|
|
||||||
this._eventListeners = [];
|
this._eventListeners = [];
|
||||||
this._pendingRequstWillBeSentEvents = new Set();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async enable() {
|
async enable() {
|
||||||
|
|
@ -51,9 +47,6 @@ class NetworkHandler {
|
||||||
this._pageNetwork.enableRequestInterception();
|
this._pageNetwork.enableRequestInterception();
|
||||||
else
|
else
|
||||||
this._pageNetwork.disableRequestInterception();
|
this._pageNetwork.disableRequestInterception();
|
||||||
// Right after we enable/disable request interception we need to await all pending
|
|
||||||
// requestWillBeSent events before successfully returning from the method.
|
|
||||||
await Promise.all(Array.from(this._pendingRequstWillBeSentEvents));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async resumeInterceptedRequest({requestId, method, headers, postData}) {
|
async resumeInterceptedRequest({requestId, method, headers, postData}) {
|
||||||
|
|
@ -69,89 +62,23 @@ class NetworkHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._contentPage.dispose();
|
|
||||||
helper.removeListeners(this._eventListeners);
|
helper.removeListeners(this._eventListeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ensureHTTPActivity(requestId) {
|
|
||||||
let activity = this._httpActivity.get(requestId);
|
|
||||||
if (!activity) {
|
|
||||||
activity = {
|
|
||||||
_id: requestId,
|
|
||||||
_lastSentEvent: null,
|
|
||||||
request: null,
|
|
||||||
response: null,
|
|
||||||
complete: null,
|
|
||||||
failed: null,
|
|
||||||
};
|
|
||||||
this._httpActivity.set(requestId, activity);
|
|
||||||
}
|
|
||||||
return activity;
|
|
||||||
}
|
|
||||||
|
|
||||||
_reportHTTPAcitivityEvents(activity) {
|
|
||||||
// State machine - sending network events.
|
|
||||||
if (!activity._lastSentEvent && activity.request) {
|
|
||||||
this._session.emitEvent('Network.requestWillBeSent', activity.request);
|
|
||||||
activity._lastSentEvent = 'requestWillBeSent';
|
|
||||||
}
|
|
||||||
if (activity._lastSentEvent === 'requestWillBeSent' && activity.response) {
|
|
||||||
this._session.emitEvent('Network.responseReceived', activity.response);
|
|
||||||
activity._lastSentEvent = 'responseReceived';
|
|
||||||
}
|
|
||||||
if (activity._lastSentEvent === 'responseReceived' && activity.complete) {
|
|
||||||
this._session.emitEvent('Network.requestFinished', activity.complete);
|
|
||||||
activity._lastSentEvent = 'requestFinished';
|
|
||||||
}
|
|
||||||
if (activity._lastSentEvent && activity.failed) {
|
|
||||||
this._session.emitEvent('Network.requestFailed', activity.failed);
|
|
||||||
activity._lastSentEvent = 'requestFailed';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up if request lifecycle is over.
|
|
||||||
if (activity._lastSentEvent === 'requestFinished' || activity._lastSentEvent === 'requestFailed')
|
|
||||||
this._httpActivity.delete(activity._id);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onRequest(eventDetails, channelKey) {
|
async _onRequest(eventDetails, channelKey) {
|
||||||
let pendingRequestCallback;
|
this._session.emitEvent('Network.requestWillBeSent', eventDetails);
|
||||||
let pendingRequestPromise = new Promise(x => pendingRequestCallback = x);
|
|
||||||
this._pendingRequstWillBeSentEvents.add(pendingRequestPromise);
|
|
||||||
let details = null;
|
|
||||||
try {
|
|
||||||
details = await this._contentPage.send('requestDetails', {channelKey});
|
|
||||||
} catch (e) {
|
|
||||||
pendingRequestCallback();
|
|
||||||
this._pendingRequstWillBeSentEvents.delete(pendingRequestPromise);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const frameId = details ? details.frameId : undefined;
|
|
||||||
const activity = this._ensureHTTPActivity(eventDetails.requestId);
|
|
||||||
activity.request = {
|
|
||||||
frameId,
|
|
||||||
...eventDetails,
|
|
||||||
};
|
|
||||||
this._reportHTTPAcitivityEvents(activity);
|
|
||||||
pendingRequestCallback();
|
|
||||||
this._pendingRequstWillBeSentEvents.delete(pendingRequestPromise);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onResponse(eventDetails) {
|
async _onResponse(eventDetails) {
|
||||||
const activity = this._ensureHTTPActivity(eventDetails.requestId);
|
this._session.emitEvent('Network.responseReceived', eventDetails);
|
||||||
activity.response = eventDetails;
|
|
||||||
this._reportHTTPAcitivityEvents(activity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onRequestFinished(eventDetails) {
|
async _onRequestFinished(eventDetails) {
|
||||||
const activity = this._ensureHTTPActivity(eventDetails.requestId);
|
this._session.emitEvent('Network.requestFinished', eventDetails);
|
||||||
activity.complete = eventDetails;
|
|
||||||
this._reportHTTPAcitivityEvents(activity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onRequestFailed(eventDetails) {
|
async _onRequestFailed(eventDetails) {
|
||||||
const activity = this._ensureHTTPActivity(eventDetails.requestId);
|
this._session.emitEvent('Network.requestFailed', eventDetails);
|
||||||
activity.failed = eventDetails;
|
|
||||||
this._reportHTTPAcitivityEvents(activity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue