- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
210 lines
6.3 KiB
JavaScript
210 lines
6.3 KiB
JavaScript
import * as PropertySymbol from '../../PropertySymbol.js';
|
|
import AsyncTaskManager from '../../async-task-manager/AsyncTaskManager.js';
|
|
import BrowserFrameURL from '../utilities/BrowserFrameURL.js';
|
|
import BrowserFrameScriptEvaluator from '../utilities/BrowserFrameScriptEvaluator.js';
|
|
import BrowserFrameNavigator from '../utilities/BrowserFrameNavigator.js';
|
|
import HistoryScrollRestorationEnum from '../../history/HistoryScrollRestorationEnum.js';
|
|
/**
|
|
* Browser frame used when constructing a Window instance without a browser.
|
|
*/
|
|
export default class DetachedBrowserFrame {
|
|
childFrames = [];
|
|
parentFrame = null;
|
|
page;
|
|
// Needs to be injected from the outside when the browser frame is constructed.
|
|
window;
|
|
[PropertySymbol.asyncTaskManager] = new AsyncTaskManager(this);
|
|
[PropertySymbol.listeners] = { navigation: [] };
|
|
[PropertySymbol.openerFrame] = null;
|
|
[PropertySymbol.openerWindow] = null;
|
|
[PropertySymbol.popup] = false;
|
|
[PropertySymbol.history] = [
|
|
{
|
|
title: '',
|
|
href: 'about:blank',
|
|
state: null,
|
|
scrollRestoration: HistoryScrollRestorationEnum.auto,
|
|
method: 'GET',
|
|
formData: null,
|
|
isCurrent: true
|
|
}
|
|
];
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param page Page.
|
|
* @param [window] Window.
|
|
*/
|
|
constructor(page) {
|
|
this.page = page;
|
|
if (page.context.browser.contexts[0]?.pages[0]?.mainFrame) {
|
|
this.window = new this.page.context.browser.windowClass(this);
|
|
}
|
|
// Attach process level error capturing.
|
|
if (page.context.browser[PropertySymbol.exceptionObserver]) {
|
|
page.context.browser[PropertySymbol.exceptionObserver].observe(this.window);
|
|
}
|
|
}
|
|
/**
|
|
* Returns the content.
|
|
*
|
|
* @returns Content.
|
|
*/
|
|
get content() {
|
|
if (!this.window) {
|
|
throw new Error('The frame has been destroyed, the "window" property is not set.');
|
|
}
|
|
return this.window.document.documentElement.outerHTML;
|
|
}
|
|
/**
|
|
* Sets the content.
|
|
*
|
|
* @param content Content.
|
|
*/
|
|
set content(content) {
|
|
if (!this.window) {
|
|
throw new Error('The frame has been destroyed, the "window" property is not set.');
|
|
}
|
|
this.window.document[PropertySymbol.isFirstWrite] = true;
|
|
this.window.document[PropertySymbol.isFirstWriteAfterOpen] = false;
|
|
this.window.document.open();
|
|
this.window.document.write(content);
|
|
}
|
|
/**
|
|
* Returns the URL.
|
|
*
|
|
* @returns URL.
|
|
*/
|
|
get url() {
|
|
if (!this.window) {
|
|
throw new Error('The frame has been destroyed, the "window" property is not set.');
|
|
}
|
|
return this.window.location.href;
|
|
}
|
|
/**
|
|
* Sets the content.
|
|
*
|
|
* @param url URL.
|
|
*/
|
|
set url(url) {
|
|
if (!this.window) {
|
|
throw new Error('The frame has been destroyed, the "window" property is not set.');
|
|
}
|
|
this.window[PropertySymbol.location][PropertySymbol.setURL](this, BrowserFrameURL.getRelativeURL(this, url).href);
|
|
}
|
|
/**
|
|
* Returns document.
|
|
*
|
|
* @returns Document.
|
|
*/
|
|
get document() {
|
|
return this.window?.document ?? null;
|
|
}
|
|
/**
|
|
* Returns a promise that is resolved when all resources has been loaded, fetch has completed, and all async tasks such as timers are complete.
|
|
*/
|
|
async waitUntilComplete() {
|
|
await Promise.all([
|
|
this[PropertySymbol.asyncTaskManager].waitUntilComplete(),
|
|
...this.childFrames.map((frame) => frame.waitUntilComplete())
|
|
]);
|
|
}
|
|
/**
|
|
* Returns a promise that is resolved when the frame has navigated and the response HTML has been written to the document.
|
|
*/
|
|
waitForNavigation() {
|
|
return new Promise((resolve) => this[PropertySymbol.listeners].navigation.push(resolve));
|
|
}
|
|
/**
|
|
* Aborts all ongoing operations.
|
|
*/
|
|
abort() {
|
|
if (!this.childFrames.length) {
|
|
return this[PropertySymbol.asyncTaskManager].abort();
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
// Using Promise instead of async/await to prevent microtask
|
|
Promise.all(this.childFrames
|
|
.map((frame) => frame.abort())
|
|
.concat([this[PropertySymbol.asyncTaskManager].abort()]))
|
|
.then(() => resolve())
|
|
.catch(reject);
|
|
});
|
|
}
|
|
/**
|
|
* Evaluates code or a VM Script in the page's context.
|
|
*
|
|
* @param script Script.
|
|
* @returns Result.
|
|
*/
|
|
evaluate(script) {
|
|
return BrowserFrameScriptEvaluator.evaluate(this, script);
|
|
}
|
|
/**
|
|
* Go to a page.
|
|
*
|
|
* @param url URL.
|
|
* @param [options] Options.
|
|
* @returns Response.
|
|
*/
|
|
goto(url, options) {
|
|
return BrowserFrameNavigator.navigate({
|
|
windowClass: this.page.context.browser.windowClass,
|
|
frame: this,
|
|
url: url,
|
|
goToOptions: options
|
|
});
|
|
}
|
|
/**
|
|
* Navigates back in history.
|
|
*
|
|
* @param [options] Options.
|
|
*/
|
|
goBack(options) {
|
|
return BrowserFrameNavigator.navigateBack({
|
|
windowClass: this.page.context.browser.windowClass,
|
|
frame: this,
|
|
goToOptions: options
|
|
});
|
|
}
|
|
/**
|
|
* Navigates forward in history.
|
|
*
|
|
* @param [options] Options.
|
|
*/
|
|
goForward(options) {
|
|
return BrowserFrameNavigator.navigateForward({
|
|
windowClass: this.page.context.browser.windowClass,
|
|
frame: this,
|
|
goToOptions: options
|
|
});
|
|
}
|
|
/**
|
|
* Navigates a delta in history.
|
|
*
|
|
* @param steps Steps.
|
|
* @param [options] Options.
|
|
*/
|
|
goSteps(steps, options) {
|
|
return BrowserFrameNavigator.navigateSteps({
|
|
windowClass: this.page.context.browser.windowClass,
|
|
frame: this,
|
|
steps: steps,
|
|
goToOptions: options
|
|
});
|
|
}
|
|
/**
|
|
* Reloads the current frame.
|
|
*
|
|
* @param [options] Options.
|
|
* @returns Response.
|
|
*/
|
|
reload(options) {
|
|
return BrowserFrameNavigator.reload({
|
|
windowClass: this.page.context.browser.windowClass,
|
|
frame: this,
|
|
goToOptions: options
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=DetachedBrowserFrame.js.map
|