Skip to content

Commit

Permalink
Merge pull request #1289 from capricorn86/1101-buttonform=id-not-trig…
Browse files Browse the repository at this point in the history
…gering-form-submit

1101 buttonform=id not triggering form submit
  • Loading branch information
capricorn86 authored Mar 8, 2024
2 parents c127bc6 + 3aa0ea9 commit 4c808b6
Show file tree
Hide file tree
Showing 60 changed files with 2,676 additions and 389 deletions.
14 changes: 12 additions & 2 deletions packages/happy-dom/src/browser/BrowserFrame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,12 @@ export default class BrowserFrame implements IBrowserFrame {
* @returns Response.
*/
public goto(url: string, options?: IGoToOptions): Promise<IResponse | null> {
return BrowserFrameNavigator.goto(BrowserWindow, this, url, options);
return BrowserFrameNavigator.navigate({
windowClass: BrowserWindow,
frame: this,
url: url,
goToOptions: options
});
}

/**
Expand All @@ -162,6 +167,11 @@ export default class BrowserFrame implements IBrowserFrame {
* @returns Response.
*/
public reload(options: IReloadOptions): Promise<IResponse | null> {
return BrowserFrameNavigator.goto(BrowserWindow, this, this.url, options);
return BrowserFrameNavigator.navigate({
windowClass: BrowserWindow,
frame: this,
url: this.url,
goToOptions: options
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,12 @@ export default class DetachedBrowserFrame implements IBrowserFrame {
* @returns Response.
*/
public goto(url: string, options?: IGoToOptions): Promise<IResponse | null> {
return BrowserFrameNavigator.goto(this.page.context.browser.windowClass, this, url, options);
return BrowserFrameNavigator.navigate({
windowClass: this.page.context.browser.windowClass,
frame: this,
url: url,
goToOptions: options
});
}

/**
Expand All @@ -177,11 +182,11 @@ export default class DetachedBrowserFrame implements IBrowserFrame {
* @returns Response.
*/
public reload(options: IReloadOptions): Promise<IResponse | null> {
return BrowserFrameNavigator.goto(
this.page.context.browser.windowClass,
this,
this.url,
options
);
return BrowserFrameNavigator.navigate({
windowClass: this.page.context.browser.windowClass,
frame: this,
url: this.url,
goToOptions: options
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,18 @@ export default class BrowserFrameFactory {
return frame[PropertySymbol.asyncTaskManager].destroy().then(() => {
frame[PropertySymbol.exceptionObserver]?.disconnect();
if (frame.window) {
const listeners = frame[PropertySymbol.listeners];

frame.window[PropertySymbol.destroy]();
(<IBrowserPage | null>frame.page) = null;
(<IBrowserWindow | null>frame.window) = null;
frame[PropertySymbol.listeners] = null;
frame[PropertySymbol.openerFrame] = null;
frame[PropertySymbol.openerWindow] = null;

for (const listener of listeners.navigation) {
listener();
}
}
resolve();
});
Expand Down
46 changes: 28 additions & 18 deletions packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,38 @@ import BrowserFrameURL from './BrowserFrameURL.js';
import BrowserFrameValidator from './BrowserFrameValidator.js';
import AsyncTaskManager from '../../async-task-manager/AsyncTaskManager.js';
import BrowserErrorCaptureEnum from '../enums/BrowserErrorCaptureEnum.js';
import FormData from '../../form-data/FormData.js';

/**
* Browser frame navigation utility.
*/
export default class BrowserFrameNavigator {
/**
* Go to a page.
* Navigates to a page.
*
* @throws Error if the request can't be resolved (because of SSL error or similar). It will not throw if the response is not ok.
* @param windowClass Window class.
* @param frame Frame.
* @param url URL.
* @param [options] Options.
* @param options Options.
* @param options.windowClass Window class.
* @param options.frame Frame.
* @param options.url URL.
* @param [options.formData] Form data.
* @param [options.method] Method.
* @param [options.goToOptions] Go to options.
* @returns Response.
*/
public static async goto(
public static async navigate(options: {
windowClass: new (
browserFrame: IBrowserFrame,
options?: { url?: string; width?: number; height?: number }
) => IBrowserWindow,
frame: IBrowserFrame,
url: string,
options?: IGoToOptions
): Promise<IResponse | null> {
) => IBrowserWindow;
frame: IBrowserFrame;
url: string;
goToOptions?: IGoToOptions;
method?: string;
formData?: FormData;
}): Promise<IResponse | null> {
const { windowClass, frame, url, formData, method, goToOptions } = options;
const referrer = goToOptions?.referrer || frame.window.location.origin;
const targetURL = BrowserFrameURL.getRelativeURL(frame, url);

if (!frame.window) {
Expand Down Expand Up @@ -98,8 +106,8 @@ export default class BrowserFrameNavigator {
(<IBrowserWindow>frame.window) = new windowClass(frame, { url: targetURL.href, width, height });
(<number>frame.window.devicePixelRatio) = devicePixelRatio;

if (options?.referrer) {
frame.window.document[PropertySymbol.referrer] = options.referrer;
if (referrer) {
frame.window.document[PropertySymbol.referrer] = referrer;
}

if (targetURL.protocol === 'about:') {
Expand All @@ -118,7 +126,7 @@ export default class BrowserFrameNavigator {

const timeout = frame.window.setTimeout(
() => abortController.abort('Request timed out.'),
options?.timeout ?? 30000
goToOptions?.timeout ?? 30000
);
const finalize = (): void => {
frame.window.clearTimeout(timeout);
Expand All @@ -132,16 +140,18 @@ export default class BrowserFrameNavigator {

try {
response = await frame.window.fetch(targetURL.href, {
referrer: options?.referrer,
referrerPolicy: options?.referrerPolicy,
referrer,
referrerPolicy: goToOptions?.referrerPolicy || 'origin',
signal: abortController.signal,
headers: options?.hard ? { 'Cache-Control': 'no-cache' } : undefined
method: method || (formData ? 'POST' : 'GET'),
headers: goToOptions?.hard ? { 'Cache-Control': 'no-cache' } : undefined,
body: formData
});

// Handles the "X-Frame-Options" header for child frames.
if (frame.parentFrame) {
const originURL = frame.parentFrame.window.location;
const xFrameOptions = response.headers.get('X-Frame-Options')?.toLowerCase();
const xFrameOptions = response.headers?.get('X-Frame-Options')?.toLowerCase();
const isSameOrigin = originURL.origin === targetURL.origin || targetURL.origin === 'null';

if (xFrameOptions === 'deny' || (xFrameOptions === 'sameorigin' && !isSameOrigin)) {
Expand Down
4 changes: 2 additions & 2 deletions packages/happy-dom/src/browser/utilities/BrowserFrameURL.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import IBrowserFrame from '../types/IBrowserFrame.js';
import { URL } from 'url';
import DOMException from '../../exception/DOMException.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import { URL } from 'url';

/**
* Browser frame URL utility.
Expand All @@ -22,7 +22,7 @@ export default class BrowserFrameURL {
}

try {
return new URL(url, frame.window.location);
return new URL(url, frame.window.location.href);
} catch (e) {
if (frame.window.location.hostname) {
throw new DOMException(
Expand Down
148 changes: 148 additions & 0 deletions packages/happy-dom/src/config/IHTMLElementTagNameMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import IHTMLAnchorElement from '../nodes/html-anchor-element/IHTMLAnchorElement.js';
import IHTMLElement from '../nodes/html-element/IHTMLElement.js';
import IHTMLAudioElement from '../nodes/html-audio-element/IHTMLAudioElement.js';
import IHTMLBaseElement from '../nodes/html-base-element/IHTMLBaseElement.js';
import IHTMLTemplateElement from '../nodes/html-template-element/IHTMLTemplateElement.js';
import IHTMLFormElement from '../nodes/html-form-element/IHTMLFormElement.js';
import IHTMLInputElement from '../nodes/html-input-element/IHTMLInputElement.js';
import IHTMLTextAreaElement from '../nodes/html-text-area-element/IHTMLTextAreaElement.js';
import IHTMLScriptElement from '../nodes/html-script-element/IHTMLScriptElement.js';
import IHTMLImageElement from '../nodes/html-image-element/IHTMLImageElement.js';
import IHTMLLinkElement from '../nodes/html-link-element/IHTMLLinkElement.js';
import IHTMLStyleElement from '../nodes/html-style-element/IHTMLStyleElement.js';
import IHTMLLabelElement from '../nodes/html-label-element/IHTMLLabelElement.js';
import IHTMLSlotElement from '../nodes/html-slot-element/IHTMLSlotElement.js';
import IHTMLMetaElement from '../nodes/html-meta-element/IHTMLMetaElement.js';
import IHTMLButtonElement from '../nodes/html-button-element/IHTMLButtonElement.js';
import IHTMLDialogElement from '../nodes/html-dialog-element/IHTMLDialogElement.js';
import IHTMLIFrameElement from '../nodes/html-iframe-element/IHTMLIFrameElement.js';
import IHTMLOptGroupElement from '../nodes/html-opt-group-element/IHTMLOptGroupElement.js';
import IHTMLOptionElement from '../nodes/html-option-element/IHTMLOptionElement.js';
import IHTMLSelectElement from '../nodes/html-select-element/IHTMLSelectElement.js';
import IHTMLVideoElement from '../nodes/html-video-element/IHTMLVideoElement.js';

// Makes it work with custom elements when they declare their own interface.
declare global {
/* eslint-disable-next-line @typescript-eslint/naming-convention */
interface HTMLElementTagNameMap {}
}

export default interface IHTMLElementTagNameMap extends HTMLElementTagNameMap {
a: IHTMLAnchorElement;
abbr: IHTMLElement;
address: IHTMLElement;
area: IHTMLElement;
article: IHTMLElement;
aside: IHTMLElement;
audio: IHTMLAudioElement;
b: IHTMLElement;
base: IHTMLBaseElement;
bdi: IHTMLElement;
bdo: IHTMLElement;
blockquaote: IHTMLElement;
body: IHTMLElement;
template: IHTMLTemplateElement;
form: IHTMLFormElement;
input: IHTMLInputElement;
textarea: IHTMLTextAreaElement;
script: IHTMLScriptElement;
img: IHTMLImageElement;
link: IHTMLLinkElement;
style: IHTMLStyleElement;
label: IHTMLLabelElement;
slot: IHTMLSlotElement;
meta: IHTMLMetaElement;
blockquote: IHTMLElement;
br: IHTMLElement;
button: IHTMLButtonElement;
canvas: IHTMLElement;
caption: IHTMLElement;
cite: IHTMLElement;
code: IHTMLElement;
col: IHTMLElement;
colgroup: IHTMLElement;
data: IHTMLElement;
datalist: IHTMLElement;
dd: IHTMLElement;
del: IHTMLElement;
details: IHTMLElement;
dfn: IHTMLElement;
dialog: IHTMLDialogElement;
div: IHTMLElement;
dl: IHTMLElement;
dt: IHTMLElement;
em: IHTMLElement;
embed: IHTMLElement;
fieldset: IHTMLElement;
figcaption: IHTMLElement;
figure: IHTMLElement;
footer: IHTMLElement;
h1: IHTMLElement;
h2: IHTMLElement;
h3: IHTMLElement;
h4: IHTMLElement;
h5: IHTMLElement;
h6: IHTMLElement;
head: IHTMLElement;
header: IHTMLElement;
hgroup: IHTMLElement;
hr: IHTMLElement;
html: IHTMLElement;
i: IHTMLElement;
iframe: IHTMLIFrameElement;
ins: IHTMLElement;
kbd: IHTMLElement;
legend: IHTMLElement;
li: IHTMLElement;
main: IHTMLElement;
map: IHTMLElement;
mark: IHTMLElement;
math: IHTMLElement;
menu: IHTMLElement;
menuitem: IHTMLElement;
meter: IHTMLElement;
nav: IHTMLElement;
noscript: IHTMLElement;
object: IHTMLElement;
ol: IHTMLElement;
optgroup: IHTMLOptGroupElement;
option: IHTMLOptionElement;
output: IHTMLElement;
p: IHTMLElement;
param: IHTMLElement;
picture: IHTMLElement;
pre: IHTMLElement;
progress: IHTMLElement;
q: IHTMLElement;
rb: IHTMLElement;
rp: IHTMLElement;
rt: IHTMLElement;
rtc: IHTMLElement;
ruby: IHTMLElement;
s: IHTMLElement;
samp: IHTMLElement;
section: IHTMLElement;
select: IHTMLSelectElement;
small: IHTMLElement;
source: IHTMLElement;
span: IHTMLElement;
strong: IHTMLElement;
sub: IHTMLElement;
summary: IHTMLElement;
sup: IHTMLElement;
table: IHTMLElement;
tbody: IHTMLElement;
td: IHTMLElement;
tfoot: IHTMLElement;
th: IHTMLElement;
thead: IHTMLElement;
time: IHTMLElement;
title: IHTMLElement;
tr: IHTMLElement;
track: IHTMLElement;
u: IHTMLElement;
ul: IHTMLElement;
var: IHTMLElement;
video: IHTMLVideoElement;
wbr: IHTMLElement;
}
Loading

0 comments on commit 4c808b6

Please sign in to comment.