debug-server-next
Version:
Dev server for hippy-core.
216 lines (215 loc) • 9.63 kB
JavaScript
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/no_underscored_properties */
import * as Common from '../common/common.js';
import { Events as ResourceTreeModelEvents, ResourceTreeModel } from './ResourceTreeModel.js';
import { TargetManager } from './TargetManager.js';
let frameManagerInstance = null;
/**
* The FrameManager is a central storage for all frames. It collects frames from all
* ResourceTreeModel-instances (one per target), so that frames can be found by id
* without needing to know their target.
*/
export class FrameManager extends Common.ObjectWrapper.ObjectWrapper {
_eventListeners;
_frames;
_framesForTarget;
_topFrame;
creationStackTraceDataForTransferringFrame;
awaitedFrames = new Map();
constructor() {
super();
this._eventListeners = new WeakMap();
TargetManager.instance().observeModels(ResourceTreeModel, this);
// Maps frameIds to frames and a count of how many ResourceTreeModels contain this frame.
// (OOPIFs are usually first attached to a new target and then detached from their old target,
// therefore being contained in 2 models for a short period of time.)
this._frames = new Map();
// Maps targetIds to a set of frameIds.
this._framesForTarget = new Map();
this._topFrame = null;
this.creationStackTraceDataForTransferringFrame = new Map();
}
static instance({ forceNew } = { forceNew: false }) {
if (!frameManagerInstance || forceNew) {
frameManagerInstance = new FrameManager();
}
return frameManagerInstance;
}
modelAdded(resourceTreeModel) {
const addListener = resourceTreeModel.addEventListener(ResourceTreeModelEvents.FrameAdded, this._frameAdded, this);
const detachListener = resourceTreeModel.addEventListener(ResourceTreeModelEvents.FrameDetached, this._frameDetached, this);
const navigatedListener = resourceTreeModel.addEventListener(ResourceTreeModelEvents.FrameNavigated, this._frameNavigated, this);
const resourceAddedListener = resourceTreeModel.addEventListener(ResourceTreeModelEvents.ResourceAdded, this._resourceAdded, this);
this._eventListeners.set(resourceTreeModel, [addListener, detachListener, navigatedListener, resourceAddedListener]);
this._framesForTarget.set(resourceTreeModel.target().id(), new Set());
}
modelRemoved(resourceTreeModel) {
const listeners = this._eventListeners.get(resourceTreeModel);
if (listeners) {
Common.EventTarget.removeEventListeners(listeners);
}
// Iterate over this model's frames and decrease their count or remove them.
// (The ResourceTreeModel does not send FrameDetached events when a model
// is removed.)
const frameSet = this._framesForTarget.get(resourceTreeModel.target().id());
if (frameSet) {
for (const frameId of frameSet) {
this._decreaseOrRemoveFrame(frameId);
}
}
this._framesForTarget.delete(resourceTreeModel.target().id());
}
_frameAdded(event) {
const frame = event.data;
const frameData = this._frames.get(frame.id);
// If the frame is already in the map, increase its count, otherwise add it to the map.
if (frameData) {
// In order to not lose frame creation stack trace information during
// an OOPIF transfer we need to copy it to the new frame
frame.setCreationStackTrace(frameData.frame.getCreationStackTraceData());
this._frames.set(frame.id, { frame, count: frameData.count + 1 });
}
else {
// If the transferring frame's detached event is received before its frame added
// event in the new target, the persisted frame creation stacktrace is reassigned.
const traceData = this.creationStackTraceDataForTransferringFrame.get(frame.id);
if (traceData && traceData.creationStackTrace) {
frame.setCreationStackTrace(traceData);
}
this._frames.set(frame.id, { frame, count: 1 });
this.creationStackTraceDataForTransferringFrame.delete(frame.id);
}
this._resetTopFrame();
// Add the frameId to the the targetId's set of frameIds.
const frameSet = this._framesForTarget.get(frame.resourceTreeModel().target().id());
if (frameSet) {
frameSet.add(frame.id);
}
this.dispatchEventToListeners(Events.FrameAddedToTarget, { frame });
this.resolveAwaitedFrame(frame);
}
_frameDetached(event) {
const frame = event.data.frame;
const isSwap = event.data.isSwap;
// Decrease the frame's count or remove it entirely from the map.
this._decreaseOrRemoveFrame(frame.id);
// If the transferring frame's detached event is received before its frame
// added event in the new target, we persist the frame creation stacktrace here
// so that later on the frame added event in the new target it can be reassigned.
if (isSwap && !this._frames.get(frame.id)) {
const traceData = frame.getCreationStackTraceData();
if (traceData.creationStackTrace) {
this.creationStackTraceDataForTransferringFrame.set(frame.id, traceData);
}
}
// Remove the frameId from the target's set of frameIds.
const frameSet = this._framesForTarget.get(frame.resourceTreeModel().target().id());
if (frameSet) {
frameSet.delete(frame.id);
}
}
_frameNavigated(event) {
const frame = event.data;
this.dispatchEventToListeners(Events.FrameNavigated, { frame });
if (frame.isTopFrame()) {
this.dispatchEventToListeners(Events.TopFrameNavigated, { frame });
}
}
_resourceAdded(event) {
const resource = event.data;
this.dispatchEventToListeners(Events.ResourceAdded, { resource });
}
_decreaseOrRemoveFrame(frameId) {
const frameData = this._frames.get(frameId);
if (frameData) {
if (frameData.count === 1) {
this._frames.delete(frameId);
this._resetTopFrame();
this.dispatchEventToListeners(Events.FrameRemoved, { frameId });
}
else {
frameData.count--;
}
}
}
/**
* Looks for the top frame in `_frames` and sets `_topFrame` accordingly.
*
* Important: This method needs to be called everytime `_frames` is updated.
*/
_resetTopFrame() {
const topFrames = this.getAllFrames().filter(frame => frame.isTopFrame());
this._topFrame = topFrames.length > 0 ? topFrames[0] : null;
}
/**
* Returns the ResourceTreeFrame with a given frameId.
* When a frame is being detached a new ResourceTreeFrame but with the same
* frameId is created. Consequently getFrame() will return a different
* ResourceTreeFrame after detachment. Callers of getFrame() should therefore
* immediately use the function return value and not store it for later use.
*/
getFrame(frameId) {
const frameData = this._frames.get(frameId);
if (frameData) {
return frameData.frame;
}
return null;
}
getAllFrames() {
return Array.from(this._frames.values(), frameData => frameData.frame);
}
getTopFrame() {
return this._topFrame;
}
async getOrWaitForFrame(frameId, notInTarget) {
const frame = this.getFrame(frameId);
if (frame && (!notInTarget || notInTarget !== frame.resourceTreeModel().target())) {
return frame;
}
return new Promise(resolve => {
const waiting = this.awaitedFrames.get(frameId);
if (waiting) {
waiting.push({ notInTarget, resolve });
}
else {
this.awaitedFrames.set(frameId, [{ notInTarget, resolve }]);
}
});
}
resolveAwaitedFrame(frame) {
const waiting = this.awaitedFrames.get(frame.id);
if (!waiting) {
return;
}
const newWaiting = waiting.filter(({ notInTarget, resolve }) => {
if (!notInTarget || notInTarget !== frame.resourceTreeModel().target()) {
resolve(frame);
return false;
}
return true;
});
if (newWaiting.length > 0) {
this.awaitedFrames.set(frame.id, newWaiting);
}
else {
this.awaitedFrames.delete(frame.id);
}
}
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export var Events;
(function (Events) {
// The FrameAddedToTarget event is sent whenever a frame is added to a target.
// This means that for OOPIFs it is sent twice: once when it's added to a
// parent target and a second time when it's added to its own target.
Events["FrameAddedToTarget"] = "FrameAddedToTarget";
Events["FrameNavigated"] = "FrameNavigated";
// The FrameRemoved event is only sent when a frame has been detached from
// all targets.
Events["FrameRemoved"] = "FrameRemoved";
Events["ResourceAdded"] = "ResourceAdded";
Events["TopFrameNavigated"] = "TopFrameNavigated";
})(Events || (Events = {}));