diff --git a/src/core/frames/frame_controller.ts b/src/core/frames/frame_controller.ts index 1b21daaab..efd68c8a1 100644 --- a/src/core/frames/frame_controller.ts +++ b/src/core/frames/frame_controller.ts @@ -68,9 +68,8 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest async loadResponse(response: FetchResponse): Promise { const fragment = fragmentFromHTML(await response.responseHTML) - const element = await this.extractForeignFrameElement(fragment) - - if (element) { + if (fragment) { + const element = await this.extractForeignFrameElement(fragment) await nextAnimationFrame() this.loadFrameElement(element) this.scrollFrameIntoView(element) @@ -195,7 +194,7 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest return getFrameElementById(id) ?? this.element } - private async extractForeignFrameElement(container: ParentNode): Promise { + private async extractForeignFrameElement(container: ParentNode): Promise { let element const id = CSS.escape(this.id) @@ -207,6 +206,9 @@ export class FrameController implements AppearanceObserverDelegate, FetchRequest await element.loaded return await this.extractForeignFrameElement(element) } + + console.error(`Response has no matching element`) + return new FrameElement() } private loadFrameElement(frameElement: FrameElement) { @@ -309,9 +311,11 @@ function readScrollLogicalPosition(value: string | null, defaultValue: ScrollLog } } -function fragmentFromHTML(html = "") { - const foreignDocument = document.implementation.createHTMLDocument() - return foreignDocument.createRange().createContextualFragment(html) +function fragmentFromHTML(html?: string) { + if (html) { + const foreignDocument = document.implementation.createHTMLDocument() + return foreignDocument.createRange().createContextualFragment(html) + } } function activateElement(element: Node | null) { diff --git a/src/tests/fixtures/frames.html b/src/tests/fixtures/frames.html index 96936221e..10c3f0916 100644 --- a/src/tests/fixtures/frames.html +++ b/src/tests/fixtures/frames.html @@ -15,5 +15,9 @@

Frames: #frame

Frames: #hello

+ + + Missing frame + diff --git a/src/tests/functional/form_submission_tests.ts b/src/tests/functional/form_submission_tests.ts index df0e1aa21..a83b291a1 100644 --- a/src/tests/functional/form_submission_tests.ts +++ b/src/tests/functional/form_submission_tests.ts @@ -17,22 +17,22 @@ export class FormSubmissionTests extends TurboDriveTestCase { } async "test standard form submission with empty created response"() { - const htmlBefore = await this.getHTMLForSelector("body") + const htmlBefore = await this.outerHTMLForSelector("body") const button = await this.querySelector("#standard form.created input[type=submit]") await button.click() await this.nextBeat - const htmlAfter = await this.getHTMLForSelector("body") + const htmlAfter = await this.outerHTMLForSelector("body") this.assert.equal(htmlAfter, htmlBefore) } async "test standard form submission with empty no-content response"() { - const htmlBefore = await this.getHTMLForSelector("body") + const htmlBefore = await this.outerHTMLForSelector("body") const button = await this.querySelector("#standard form.no-content input[type=submit]") await button.click() await this.nextBeat - const htmlAfter = await this.getHTMLForSelector("body") + const htmlAfter = await this.outerHTMLForSelector("body") this.assert.equal(htmlAfter, htmlBefore) } @@ -75,22 +75,22 @@ export class FormSubmissionTests extends TurboDriveTestCase { } async "test frame form submission with empty created response"() { - const htmlBefore = await this.getHTMLForSelector("#frame") + const htmlBefore = await this.outerHTMLForSelector("#frame") const button = await this.querySelector("#frame form.created input[type=submit]") await button.click() await this.nextBeat - const htmlAfter = await this.getHTMLForSelector("#frame") + const htmlAfter = await this.outerHTMLForSelector("#frame") this.assert.equal(htmlAfter, htmlBefore) } async "test frame form submission with empty no-content response"() { - const htmlBefore = await this.getHTMLForSelector("#frame") + const htmlBefore = await this.outerHTMLForSelector("#frame") const button = await this.querySelector("#frame form.no-content input[type=submit]") await button.click() await this.nextBeat - const htmlAfter = await this.getHTMLForSelector("#frame") + const htmlAfter = await this.outerHTMLForSelector("#frame") this.assert.equal(htmlAfter, htmlBefore) } diff --git a/src/tests/functional/frame_tests.ts b/src/tests/functional/frame_tests.ts new file mode 100644 index 000000000..a6572cf49 --- /dev/null +++ b/src/tests/functional/frame_tests.ts @@ -0,0 +1,15 @@ +import { FunctionalTestCase } from "../helpers/functional_test_case" + +export class FrameTests extends FunctionalTestCase { + async setup() { + await this.goToLocation("/src/tests/fixtures/frames.html") + } + + async "test following a link to a page without a matching frame results in an empty frame"() { + await this.clickSelector("#missing a") + await this.nextBeat + this.assert.notOk(await this.innerHTMLForSelector("#missing")) + } +} + +FrameTests.registerSuite() diff --git a/src/tests/functional/index.ts b/src/tests/functional/index.ts index 27590e1c6..c8a135c9b 100644 --- a/src/tests/functional/index.ts +++ b/src/tests/functional/index.ts @@ -1,5 +1,6 @@ export * from "./async_script_tests" export * from "./form_submission_tests" +export * from "./frame_tests" export * from "./loading_tests" export * from "./navigation_tests" export * from "./rendering_tests" diff --git a/src/tests/helpers/functional_test_case.ts b/src/tests/helpers/functional_test_case.ts index 60a6028cf..9d1a6edef 100644 --- a/src/tests/helpers/functional_test_case.ts +++ b/src/tests/helpers/functional_test_case.ts @@ -41,11 +41,16 @@ export class FunctionalTestCase extends InternTestCase { return this.evaluate(element => element.scrollIntoView(), element) } - async getHTMLForSelector(selector: string): Promise { + async outerHTMLForSelector(selector: string): Promise { const element = await this.remote.findByCssSelector(selector) return this.evaluate(element => element.outerHTML, element) } + async innerHTMLForSelector(selector: string): Promise { + const element = await this.remote.findAllByCssSelector(selector) + return this.evaluate(element => element.innerHTML, element) + } + get scrollPosition(): Promise<{ x: number, y: number }> { return this.evaluate(() => ({ x: window.scrollX, y: window.scrollY })) }