/** * Copyright 2017 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var EventEmitter = require('events'); class FrameManager extends EventEmitter { /** * @param {!Connection} client * @return {!FrameManager} */ static async create(client) { var mainFramePayload = await client.send('Page.getResourceTree'); return new FrameManager(client, mainFramePayload.frameTree); } /** * @param {!Connection} client * @param {!Object} frameTree */ constructor(client, frameTree) { super(); this._client = client; /** @type {!Map} */ this._frames = new Map(); this._mainFrame = this._addFramesRecursively(null, frameTree); this._client.on('Page.frameAttached', event => this._frameAttached(event.frameId, event.parentFrameId)); this._client.on('Page.frameNavigated', event => this._frameNavigated(event.frame)); this._client.on('Page.frameDetached', event => this._frameDetached(event.frameId)); } /** * @return {!Frame} */ mainFrame() { return this._mainFrame; } /** * @return {!Array} */ frames() { return Array.from(this._frames.values()); } /** * @param {string} frameId * @param {?string} parentFrameId * @return {?Frame} */ _frameAttached(frameId, parentFrameId) { if (this._frames.has(frameId)) return; if (!parentFrameId) { // Navigation to the new backend process. this._navigateFrame(this._mainFrame, frameId, null); return; } var parentFrame = this._frames.get(parentFrameId); var frame = new Frame(parentFrame, frameId, null); this._frames.set(frame._id, frame); this.emit(FrameManager.Events.FrameAttached, frame); } /** * @param {!Object} framePayload */ _frameNavigated(framePayload) { var frame = this._frames.get(framePayload.id); if (!frame) { // Simulate missed "frameAttached" for a main frame navigation to the new backend process. console.assert(!framePayload.parentId, 'Main frame shouldn\'t have parent frame id.'); frame = this._mainFrame; } this._navigateFrame(frame, framePayload.id, framePayload); } /** * @param {string} frameId */ _frameDetached(frameId) { var frame = this._frames.get(frameId); if (frame) this._removeFramesRecursively(frame); } /** * @param {!Frame} frame * @param {string} newFrameId * @param {?Object} newFramePayload */ _navigateFrame(frame, newFrameId, newFramePayload) { // Detach all child frames first. for (var child of frame.childFrames()) this._removeFramesRecursively(child); this._frames.delete(frame._id, frame); frame._id = newFrameId; frame._adoptPayload(newFramePayload); this._frames.set(newFrameId, frame); this.emit(FrameManager.Events.FrameNavigated, frame); } /** * @param {?Frame} parentFrame * @param {!Object} frameTreePayload * @return {!Frame} */ _addFramesRecursively(parentFrame, frameTreePayload) { var framePayload = frameTreePayload.frame; var frame = new Frame(parentFrame, framePayload.id, framePayload); this._frames.set(frame._id, frame); for (var i = 0; frameTreePayload.childFrames && i < frameTreePayload.childFrames.length; ++i) this._addFramesRecursively(frame, frameTreePayload.childFrames[i]); return frame; } /** * @param {!Frame} frame */ _removeFramesRecursively(frame) { for (var child of frame.childFrames()) this._removeFramesRecursively(child); frame._detach(); this._frames.delete(frame._id); this.emit(FrameManager.Events.FrameDetached, frame); } } /** @enum {string} */ FrameManager.Events = { FrameAttached: 'frameattached', FrameNavigated: 'framenavigated', FrameDetached: 'framedetached' }; /** * @unrestricted */ class Frame { /** * @param {?Frame} parentFrame * @param {string} frameId * @param {?Object} payload */ constructor(parentFrame, frameId, payload) { this._parentFrame = parentFrame; this._url = ''; this._id = frameId; this._adoptPayload(payload); /** @type {!Set} */ this._childFrames = new Set(); if (this._parentFrame) this._parentFrame._childFrames.add(this); } /** * @return {string} */ name() { return this._name || ''; } /** * @return {string} */ url() { return this._url; } /** * @return {string} */ securityOrigin() { return this._securityOrigin; } /** * @return {?Frame} */ parentFrame() { return this._parentFrame; } /** * @return {!Array.} */ childFrames() { return Array.from(this._childFrames); } /** * @return {boolean} */ isMainFrame() { return !this._detached && !this._parentFrame; } /** * @return {boolean} */ isDetached() { return this._detached; } /** * @param {?Object} framePayload */ _adoptPayload(framePayload) { framePayload = framePayload || { name: '', url: '', securityOrigin: '', mimeType: '' }; this._name = framePayload.name; this._url = framePayload.url; this._securityOrigin = framePayload.securityOrigin; this._mimeType = framePayload.mimeType; } _detach() { this._detached = true; if (this._parentFrame) this._parentFrame._childFrames.delete(this); this._parentFrame = null; } } module.exports = FrameManager;