blob: a04c308b8d5383b728f689d376e469d75c3a1fc6 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Protocol from '../../generated/protocol.js';
import {createTarget} from '../../testing/EnvironmentHelpers.js';
import {
describeWithMockConnection,
setMockConnectionResponseHandler,
} from '../../testing/MockConnection.js';
import {
addChildFrame,
getInitializedResourceTreeModel,
getMainFrame,
LOADER_ID,
MAIN_FRAME_ID,
navigate,
} from '../../testing/ResourceTreeHelpers.js';
import * as SDK from './sdk.js';
describeWithMockConnection('ResourceTreeModel', () => {
it('calls clearRequests on reloadPage', async () => {
const resourceTreeModel = await getInitializedResourceTreeModel(createTarget());
const clearRequests = sinon.stub(SDK.NetworkManager.NetworkManager.prototype, 'clearRequests');
resourceTreeModel.reloadPage();
assert.isTrue(clearRequests.calledOnce, 'Not called just once');
});
it('calls clearRequests on top frame navigated', () => {
const target = createTarget();
const clearRequests = sinon.stub(SDK.NetworkManager.NetworkManager.prototype, 'clearRequests');
navigate(getMainFrame(target));
assert.isTrue(clearRequests.calledOnce, 'Not called just once');
});
it('does not call clearRequests on non-top frame navigated', async () => {
const target = createTarget();
const clearRequests = sinon.stub(SDK.NetworkManager.NetworkManager.prototype, 'clearRequests');
navigate(await addChildFrame(target));
assert.isTrue(clearRequests.notCalled, 'Called unexpctedly');
});
it('added frame has storageKey when navigated', async () => {
const testKey = 'test-storage-key';
const target = createTarget();
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
assert.isEmpty(resourceTreeModel!.frames());
setMockConnectionResponseHandler('Storage.getStorageKey', () => ({storageKey: testKey}));
navigate(getMainFrame(target));
const frames = resourceTreeModel!.frames();
assert.lengthOf(frames, 1);
const addedFrame = frames[0];
const key = await addedFrame.getStorageKey(false);
assert.strictEqual(key, testKey);
});
it('storage key gets updated when frame tree changes', async () => {
const testKey = 'test-storage-key';
const target = createTarget();
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
assert.isEmpty(resourceTreeModel?.frames());
const manager = target.model(SDK.StorageKeyManager.StorageKeyManager);
assert.exists(manager);
const storageKeyAddedPromise = new Promise<void>(resolve => {
manager.addEventListener(SDK.StorageKeyManager.Events.STORAGE_KEY_ADDED, () => {
resolve();
});
});
setMockConnectionResponseHandler('Storage.getStorageKey', () => ({storageKey: testKey}));
navigate(getMainFrame(target));
await storageKeyAddedPromise;
assert.strictEqual(resourceTreeModel?.frames().length, 1);
const mainStorageKeyChangedPromise = new Promise<void>(resolve => {
manager.addEventListener(SDK.StorageKeyManager.Events.MAIN_STORAGE_KEY_CHANGED, () => {
resolve();
});
});
const storageKeyRemovedPromise = new Promise<void>(resolve => {
manager.addEventListener(SDK.StorageKeyManager.Events.STORAGE_KEY_REMOVED, () => {
resolve();
});
});
resourceTreeModel?.frameDetached('main' as Protocol.Page.FrameId, false);
assert.isEmpty(resourceTreeModel?.frames());
await Promise.all([mainStorageKeyChangedPromise, storageKeyRemovedPromise]);
});
function getResourceTreeModel(target: SDK.Target.Target): SDK.ResourceTreeModel.ResourceTreeModel {
const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel);
assert.exists(resourceTreeModel);
return resourceTreeModel;
}
it('calls reloads only top frames', () => {
const tabTarget = createTarget({type: SDK.Target.Type.TAB});
const mainFrameTarget = createTarget({parentTarget: tabTarget});
const subframeTarget = createTarget({parentTarget: mainFrameTarget});
const reloadMainFramePage = sinon.spy(getResourceTreeModel(mainFrameTarget), 'reloadPage');
const reloadSubframePage = sinon.spy(getResourceTreeModel(subframeTarget), 'reloadPage');
SDK.ResourceTreeModel.ResourceTreeModel.reloadAllPages();
sinon.assert.calledOnce(reloadMainFramePage);
sinon.assert.notCalled(reloadSubframePage);
});
it('tags reloads with the targets loaderId', async () => {
const target = createTarget();
const resourceTreeModel = await getInitializedResourceTreeModel(target);
const reload = sinon.spy(target.pageAgent(), 'invoke_reload');
assert.isNotNull(resourceTreeModel.mainFrame);
resourceTreeModel.reloadPage();
sinon.assert.calledOnce(reload);
assert.deepEqual(
reload.args[0], [{ignoreCache: undefined, loaderId: LOADER_ID, scriptToEvaluateOnLoad: undefined}]);
});
it('identifies not top frame', async () => {
const tabTarget = createTarget({type: SDK.Target.Type.TAB});
const mainFrameTarget = createTarget({parentTarget: tabTarget});
const subframeTarget = createTarget({parentTarget: mainFrameTarget});
navigate(getMainFrame(mainFrameTarget));
navigate(getMainFrame(subframeTarget), {parentId: 'parentId' as Protocol.Page.FrameId});
assert.isTrue(getResourceTreeModel(mainFrameTarget).mainFrame!.isOutermostFrame());
assert.isFalse(getResourceTreeModel(subframeTarget).mainFrame!.isOutermostFrame());
});
it('emits PrimaryPageChanged event upon prerender activation', async () => {
SDK.ChildTargetManager.ChildTargetManager.install();
const tabTarget = createTarget({type: SDK.Target.Type.TAB});
const childTargetManager = tabTarget.model(SDK.ChildTargetManager.ChildTargetManager);
assert.exists(childTargetManager);
const targetId = 'target_id' as Protocol.Target.TargetID;
const targetInfo = {
targetId,
type: 'page',
title: 'title',
url: 'http://example.com/prerendered.html',
attached: true,
canAccessOpener: false,
subtype: 'prerender',
};
childTargetManager.targetCreated({targetInfo});
await childTargetManager.attachedToTarget(
{sessionId: 'session_id' as Protocol.Target.SessionID, targetInfo, waitingForDebugger: false});
const prerenderTarget = SDK.TargetManager.TargetManager.instance().targetById(targetId);
assert.exists(prerenderTarget);
const resourceTreeModel = prerenderTarget.model(SDK.ResourceTreeModel.ResourceTreeModel);
assert.exists(resourceTreeModel);
const primaryPageChangedEvents:
Array<{frame: SDK.ResourceTreeModel.ResourceTreeFrame, type: SDK.ResourceTreeModel.PrimaryPageChangeType}> = [];
resourceTreeModel.addEventListener(
SDK.ResourceTreeModel.Events.PrimaryPageChanged, event => primaryPageChangedEvents.push(event.data));
const frame = resourceTreeModel.frameAttached('frame_id' as Protocol.Page.FrameId, null);
childTargetManager.targetInfoChanged({targetInfo: {...targetInfo, subtype: undefined}});
assert.lengthOf(primaryPageChangedEvents, 1);
assert.strictEqual(primaryPageChangedEvents[0].frame, frame);
assert.strictEqual(primaryPageChangedEvents[0].type, SDK.ResourceTreeModel.PrimaryPageChangeType.ACTIVATION);
});
it('emits PrimaryPageChanged event only upon navigation of the primary frame', async () => {
const tabTarget = createTarget({type: SDK.Target.Type.TAB});
const mainFrameTarget = createTarget({parentTarget: tabTarget});
const subframeTarget = createTarget({parentTarget: mainFrameTarget});
const prerenderTarget = createTarget({parentTarget: tabTarget, subtype: 'prerender'});
const primaryPageChangedEvents:
Array<{frame: SDK.ResourceTreeModel.ResourceTreeFrame, type: SDK.ResourceTreeModel.PrimaryPageChangeType}> = [];
[getResourceTreeModel(mainFrameTarget), getResourceTreeModel(subframeTarget), getResourceTreeModel(prerenderTarget)]
.forEach(resourceTreeModel => {
resourceTreeModel.addEventListener(
SDK.ResourceTreeModel.Events.PrimaryPageChanged, event => primaryPageChangedEvents.push(event.data));
});
navigate(getMainFrame(mainFrameTarget));
assert.lengthOf(primaryPageChangedEvents, 1);
assert.strictEqual(primaryPageChangedEvents[0].frame.id, 'main');
assert.strictEqual(primaryPageChangedEvents[0].type, SDK.ResourceTreeModel.PrimaryPageChangeType.NAVIGATION);
navigate(getMainFrame(subframeTarget), {parentId: MAIN_FRAME_ID, id: 'child' as Protocol.Page.FrameId});
assert.lengthOf(primaryPageChangedEvents, 1);
navigate(getMainFrame(prerenderTarget));
assert.lengthOf(primaryPageChangedEvents, 1);
});
it('rebuilds the resource tree upon bfcache-navigation', async () => {
const target = createTarget();
const frameManager = SDK.FrameManager.FrameManager.instance();
const removedFromFrameManagerSpy = sinon.spy(frameManager, 'modelRemoved');
const addedToFrameManagerSpy = sinon.spy(frameManager, 'modelAdded');
const resourceTreeModel = getResourceTreeModel(target);
await resourceTreeModel.once(SDK.ResourceTreeModel.Events.CachedResourcesLoaded);
const cachedResourcesLoaded = resourceTreeModel.once(SDK.ResourceTreeModel.Events.CachedResourcesLoaded);
const processPendingEventsSpy = sinon.spy(resourceTreeModel, 'processPendingEvents');
const initialFrame = resourceTreeModel.frames()[0];
navigate(getMainFrame(target), {}, Protocol.Page.NavigationType.BackForwardCacheRestore);
await cachedResourcesLoaded;
sinon.assert.calledOnce(removedFromFrameManagerSpy);
sinon.assert.calledOnce(addedToFrameManagerSpy);
sinon.assert.calledTwice(processPendingEventsSpy);
const frameAfterNav = resourceTreeModel.frames()[0];
assert.strictEqual(
initialFrame, frameAfterNav,
'Instead of keeping the existing frame, a new frame was created upon bfcache-navigation');
});
});