Skip to content

Commit

Permalink
View, PageView
Browse files Browse the repository at this point in the history
  • Loading branch information
sstephenson committed Jan 25, 2021
1 parent 269fa24 commit a7d02f0
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 226 deletions.
49 changes: 15 additions & 34 deletions src/core/drive/error_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,20 @@
import { RenderCallback, RenderDelegate, Renderer } from "../renderer"
import { PageSnapshot } from "./page_snapshot"
import { Renderer } from "../renderer"

export class ErrorRenderer extends Renderer {
readonly delegate: RenderDelegate
readonly htmlElement: HTMLHtmlElement
readonly newHead: HTMLHeadElement
readonly newBody: HTMLBodyElement

static render(delegate: RenderDelegate, callback: RenderCallback, html: string) {
return new this(delegate, html).render(callback)
}

constructor(delegate: RenderDelegate, html: string) {
super()
this.delegate = delegate
this.htmlElement = (() => {
const htmlElement = document.createElement("html")
htmlElement.innerHTML = html
return htmlElement
})()
this.newHead = this.htmlElement.querySelector("head") || document.createElement("head")
this.newBody = this.htmlElement.querySelector("body") || document.createElement("body")
}

render(callback: RenderCallback) {
this.renderView(() => {
this.replaceHeadAndBody()
this.activateBodyScriptElements()
callback()
})
export class ErrorRenderer extends Renderer<PageSnapshot> {
async render() {
this.replaceHeadAndBody()
this.activateScriptElements()
}

replaceHeadAndBody() {
const { documentElement, head, body } = document
documentElement.replaceChild(this.newHead, head)
documentElement.replaceChild(this.newBody, body)
documentElement.replaceChild(this.toHead, head)
documentElement.replaceChild(this.toRootNode, body)
}

activateBodyScriptElements() {
for (const replaceableElement of this.getScriptElements()) {
activateScriptElements() {
for (const replaceableElement of this.scriptElements) {
const parentNode = replaceableElement.parentNode
if (parentNode) {
const element = this.createScriptElement(replaceableElement)
Expand All @@ -46,7 +23,11 @@ export class ErrorRenderer extends Renderer {
}
}

getScriptElements() {
get toHead() {
return this.toSnapshot.headSnapshot.rootNode
}

get scriptElements() {
return [ ...document.documentElement.querySelectorAll("script") ]
}
}
2 changes: 1 addition & 1 deletion src/core/drive/navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class Navigator {

if (responseHTML) {
const snapshot = PageSnapshot.fromHTMLString(responseHTML)
this.view.render({ snapshot }, () => {})
await this.view.renderPage(snapshot)
this.view.clearSnapshotCache()
}
}
Expand Down
77 changes: 29 additions & 48 deletions src/core/drive/page_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,40 @@
import { RenderCallback, RenderDelegate, Renderer } from "../renderer"
import { Renderer } from "../renderer"
import { PageSnapshot } from "./page_snapshot"

export { RenderCallback, RenderDelegate } from "../renderer"

export type PermanentElement = Element & { id: string }

export type Placeholder = { element: Element, permanentElement: PermanentElement }

export class PageRenderer extends Renderer {
readonly delegate: RenderDelegate
readonly currentSnapshot: PageSnapshot
readonly newSnapshot: PageSnapshot
readonly isPreview: boolean
export class PageRenderer extends Renderer<PageSnapshot> {
get shouldRender() {
return this.toSnapshot.isVisitable && this.trackedElementsAreIdentical
}

static render(delegate: RenderDelegate, callback: RenderCallback, currentSnapshot: PageSnapshot, newSnapshot: PageSnapshot, isPreview: boolean) {
return new this(delegate, currentSnapshot, newSnapshot, isPreview).render(callback)
prepareToRender() {
this.mergeHead()
}

constructor(delegate: RenderDelegate, currentSnapshot: PageSnapshot, newSnapshot: PageSnapshot, isPreview: boolean) {
super()
this.delegate = delegate
this.currentSnapshot = currentSnapshot
this.newSnapshot = newSnapshot
this.isPreview = isPreview
async render() {
this.replaceBody()
}

get currentHeadSnapshot() {
return this.currentSnapshot.headSnapshot
finishRendering() {
super.finishRendering()
if (this.isPreview) {
this.focusFirstAutofocusableElement()
}
}

get newHeadSnapshot() {
return this.newSnapshot.headSnapshot
get currentHeadSnapshot() {
return this.fromSnapshot.headSnapshot
}

get newBody() {
return this.newSnapshot.rootNode as HTMLBodyElement
get newHeadSnapshot() {
return this.toSnapshot.headSnapshot
}

render(callback: RenderCallback) {
if (this.shouldRender()) {
this.mergeHead()
this.renderView(() => {
this.replaceBody()
if (!this.isPreview) {
this.focusFirstAutofocusableElement()
}
callback()
})
} else {
this.invalidateView()
}
get toRootNode() {
return this.toSnapshot.rootNode
}

mergeHead() {
Expand All @@ -66,11 +51,7 @@ export class PageRenderer extends Renderer {
this.replacePlaceholderElementsWithClonedPermanentElements(placeholders)
}

shouldRender() {
return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical()
}

trackedElementsAreIdentical() {
get trackedElementsAreIdentical() {
return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature
}

Expand Down Expand Up @@ -100,7 +81,7 @@ export class PageRenderer extends Renderer {

relocateCurrentBodyPermanentElements() {
return this.getCurrentBodyPermanentElements().reduce((placeholders, permanentElement) => {
const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id)
const newElement = this.toSnapshot.getPermanentElementById(permanentElement.id)
if (newElement) {
const placeholder = createPlaceholderForPermanentElement(permanentElement)
replaceElementWithElement(permanentElement, placeholder.element)
Expand All @@ -120,7 +101,7 @@ export class PageRenderer extends Renderer {
}

activateNewBody() {
document.adoptNode(this.newBody)
document.adoptNode(this.toRootNode)
this.activateNewBodyScriptElements()
}

Expand All @@ -132,15 +113,15 @@ export class PageRenderer extends Renderer {
}

assignNewBody() {
if (document.body) {
replaceElementWithElement(document.body, this.newBody)
if (document.body && this.toRootNode instanceof HTMLBodyElement) {
replaceElementWithElement(document.body, this.toRootNode)
} else {
document.documentElement.appendChild(this.newBody)
document.documentElement.appendChild(this.toRootNode)
}
}

focusFirstAutofocusableElement() {
const element = this.newSnapshot.firstAutofocusableElement
const element = this.toSnapshot.firstAutofocusableElement
if (elementIsFocusable(element)) {
element.focus()
}
Expand All @@ -163,11 +144,11 @@ export class PageRenderer extends Renderer {
}

getCurrentBodyPermanentElements(): PermanentElement[] {
return this.currentSnapshot.getPermanentElementsPresentInSnapshot(this.newSnapshot)
return this.fromSnapshot.getPermanentElementsPresentInSnapshot(this.toSnapshot)
}

getNewBodyScriptElements() {
return [ ...this.newBody.querySelectorAll("script") ]
return [ ...this.toRootNode.querySelectorAll("script") ]
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/core/drive/page_snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { expandURL } from "../url"
import { HeadSnapshot } from "./head_snapshot"

export class PageSnapshot extends Snapshot {
static fromHTMLString(html: string) {
static fromHTMLString(html = "") {
const document = new DOMParser().parseFromString(html, "text/html")
return this.fromDocument(document)
}
Expand Down
52 changes: 52 additions & 0 deletions src/core/drive/page_view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { nextMicrotask } from "../../util"
import { View, ViewDelegate } from "../view"
import { ErrorRenderer } from "./error_renderer"
import { PageRenderer } from "./page_renderer"
import { PageSnapshot } from "./page_snapshot"
import { SnapshotCache } from "./snapshot_cache"

export interface PageViewDelegate extends ViewDelegate<PageSnapshot> {
viewWillCacheSnapshot(): void
}

type PageViewRenderer = PageRenderer | ErrorRenderer

export class PageView extends View<PageSnapshot, PageViewDelegate, PageViewRenderer> {
readonly snapshotCache = new SnapshotCache(10)
lastRenderedLocation = new URL(location.href)

renderPage(snapshot: PageSnapshot, isPreview = false) {
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview)
return this.render(renderer)
}

renderError(snapshot: PageSnapshot) {
const renderer = new ErrorRenderer(this.snapshot, snapshot, false)
this.render(renderer)
}

clearSnapshotCache() {
this.snapshotCache.clear()
}

async cacheSnapshot() {
if (this.shouldCacheSnapshot) {
this.delegate.viewWillCacheSnapshot()
const { snapshot, lastRenderedLocation: location } = this
await nextMicrotask()
this.snapshotCache.put(location, snapshot.clone())
}
}

getCachedSnapshotForLocation(location: URL) {
return this.snapshotCache.get(location)
}

get snapshot() {
return PageSnapshot.fromHTMLElement(this.element)
}

get shouldCacheSnapshot() {
return this.snapshot.isCacheable
}
}
103 changes: 0 additions & 103 deletions src/core/drive/view.ts

This file was deleted.

Loading

0 comments on commit a7d02f0

Please sign in to comment.