@quick-game/cli
Version:
Command line interface for rapid qg development
212 lines • 9.85 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.
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 = new WeakMap();
// 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.)
#frames = new Map();
#framesForTarget = new Map();
#outermostFrame = null;
#transferringFramesDataCache = new Map();
#awaitedFrames = new Map();
constructor() {
super();
TargetManager.instance().observeModels(ResourceTreeModel, this);
}
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 the following attributes of a frame during
// an OOPIF transfer we need to copy them 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 frame's cached attributes are reassigned.
const cachedFrameAttributes = this.#transferringFramesDataCache.get(frame.id);
if (cachedFrameAttributes?.creationStackTrace && cachedFrameAttributes?.creationStackTraceTarget) {
frame.setCreationStackTrace({
creationStackTrace: cachedFrameAttributes.creationStackTrace,
creationStackTraceTarget: cachedFrameAttributes.creationStackTraceTarget,
});
}
this.#frames.set(frame.id, { frame, count: 1 });
this.#transferringFramesDataCache.delete(frame.id);
}
this.resetOutermostFrame();
// 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, isSwap } = event.data;
// 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 some attributes of the frame here
// so that later on the frame added event in the new target they can be reassigned.
if (isSwap && !this.#frames.get(frame.id)) {
const traceData = frame.getCreationStackTraceData();
const cachedFrameAttributes = {
...(traceData.creationStackTrace && { creationStackTrace: traceData.creationStackTrace }),
...(traceData.creationStackTrace && { creationStackTraceTarget: traceData.creationStackTraceTarget }),
};
this.#transferringFramesDataCache.set(frame.id, cachedFrameAttributes);
}
// 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.isOutermostFrame()) {
this.dispatchEventToListeners(Events.OutermostFrameNavigated, { frame });
}
}
resourceAdded(event) {
this.dispatchEventToListeners(Events.ResourceAdded, { resource: event.data });
}
decreaseOrRemoveFrame(frameId) {
const frameData = this.#frames.get(frameId);
if (frameData) {
if (frameData.count === 1) {
this.#frames.delete(frameId);
this.resetOutermostFrame();
this.dispatchEventToListeners(Events.FrameRemoved, { frameId });
}
else {
frameData.count--;
}
}
}
/**
* Looks for the outermost frame in `#frames` and sets `#outermostFrame` accordingly.
*
* Important: This method needs to be called everytime `#frames` is updated.
*/
resetOutermostFrame() {
const outermostFrames = this.getAllFrames().filter(frame => frame.isOutermostFrame());
this.#outermostFrame = outermostFrames.length > 0 ? outermostFrames[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);
}
getOutermostFrame() {
return this.#outermostFrame;
}
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["OutermostFrameNavigated"] = "OutermostFrameNavigated";
})(Events || (Events = {}));
//# sourceMappingURL=FrameManager.js.map