diff --git a/src/core/drive/visit.ts b/src/core/drive/visit.ts
index 23811eec8..99d3a597e 100644
--- a/src/core/drive/visit.ts
+++ b/src/core/drive/visit.ts
@@ -2,7 +2,7 @@ import { Adapter } from "../native/adapter"
import { FetchMethod, FetchRequest, FetchRequestDelegate } from "../../http/fetch_request"
import { FetchResponse } from "../../http/fetch_response"
import { History } from "./history"
-import { getAnchor } from "../url"
+import { getAnchor, getRequestURL } from "../url"
import { PageSnapshot } from "./page_snapshot"
import { Action } from "../types"
import { uuid } from "../../util"
@@ -39,7 +39,8 @@ export type VisitOptions = {
historyChanged: boolean,
referrer?: URL,
snapshotHTML?: string,
- response?: VisitResponse
+ response?: VisitResponse,
+ restorationIdentifier?: string,
}
const defaultOptions: VisitOptions = {
@@ -70,6 +71,7 @@ export class Visit implements FetchRequestDelegate {
frame?: number
historyChanged = false
location: URL
+ isSamePage: boolean
redirectedToLocation?: URL
request?: FetchRequest
response?: VisitResponse
@@ -81,6 +83,11 @@ export class Visit implements FetchRequestDelegate {
constructor(delegate: VisitDelegate, location: URL, restorationIdentifier: string | undefined, options: Partial = {}) {
this.delegate = delegate
this.location = location
+ this.isSamePage = (
+ getAnchor(location) != null &&
+ getRequestURL(location) === getRequestURL(this.view.lastRenderedLocation)
+ )
+
this.restorationIdentifier = restorationIdentifier || uuid()
const { action, historyChanged, referrer, snapshotHTML, response } = { ...defaultOptions, ...options }
@@ -251,6 +258,15 @@ export class Visit implements FetchRequestDelegate {
}
}
+ goToSamePageAnchor() {
+ if (this.isSamePage) {
+ this.render(async () => {
+ this.cacheSnapshot()
+ this.adapter.visitRendered(this)
+ })
+ }
+ }
+
// Fetch request delegate
requestStarted() {
@@ -310,8 +326,10 @@ export class Visit implements FetchRequestDelegate {
}
scrollToAnchor() {
- if (getAnchor(this.location)) {
- this.view.scrollToAnchor(getAnchor(this.location))
+ const anchor = getAnchor(this.location);
+
+ if (anchor) {
+ this.view.scrollToAnchor(anchor)
return true
}
}
@@ -345,9 +363,13 @@ export class Visit implements FetchRequestDelegate {
}
shouldIssueRequest() {
- return this.action == "restore"
- ? !this.hasCachedSnapshot()
- : true
+ if (this.isSamePage) {
+ return false
+ } else if (this.action == "restore") {
+ return !this.hasCachedSnapshot()
+ } else {
+ return true
+ }
}
cacheSnapshot() {
diff --git a/src/core/native/browser_adapter.ts b/src/core/native/browser_adapter.ts
index 44c2bee87..cbb7a708b 100644
--- a/src/core/native/browser_adapter.ts
+++ b/src/core/native/browser_adapter.ts
@@ -21,6 +21,7 @@ export class BrowserAdapter implements Adapter {
visitStarted(visit: Visit) {
visit.issueRequest()
visit.changeHistory()
+ visit.goToSamePageAnchor()
visit.loadCachedSnapshot()
}
diff --git a/src/core/session.ts b/src/core/session.ts
index a00fb4fba..44f16ea01 100644
--- a/src/core/session.ts
+++ b/src/core/session.ts
@@ -109,9 +109,9 @@ export class Session implements FormSubmitObserverDelegate, HistoryDelegate, Lin
// History delegate
- historyPoppedToLocationWithRestorationIdentifier(location: URL) {
+ historyPoppedToLocationWithRestorationIdentifier(location: URL, restorationIdentifier: string) {
if (this.enabled) {
- this.navigator.proposeVisit(location, { action: "restore", historyChanged: true })
+ this.navigator.proposeVisit(location, { action: "restore", historyChanged: true, restorationIdentifier })
} else {
this.adapter.pageInvalidated()
}
diff --git a/src/core/snapshot.ts b/src/core/snapshot.ts
index 0bb51ca40..973ed9805 100644
--- a/src/core/snapshot.ts
+++ b/src/core/snapshot.ts
@@ -9,11 +9,11 @@ export class Snapshot {
return [ ...this.element.children ]
}
- hasAnchor(anchor: string) {
+ hasAnchor(anchor: string | undefined) {
return this.getElementForAnchor(anchor) != null
}
- getElementForAnchor(anchor: string) {
+ getElementForAnchor(anchor: string | undefined) {
try {
return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`)
} catch {
diff --git a/src/core/url.ts b/src/core/url.ts
index 8efb81d2e..6c96331ce 100644
--- a/src/core/url.ts
+++ b/src/core/url.ts
@@ -10,8 +10,6 @@ export function getAnchor(url: URL) {
return url.hash.slice(1)
} else if (anchorMatch = url.href.match(/#(.*)$/)) {
return anchorMatch[1]
- } else {
- return ""
}
}
@@ -28,13 +26,15 @@ export function isPrefixedBy(baseURL: URL, url: URL) {
return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix)
}
+export function getRequestURL(url: URL) {
+ const anchor = getAnchor(url)
+ return anchor != null
+ ? url.href.slice(0, -(anchor.length + 1))
+ : url.href
+}
+
export function toCacheKey(url: URL) {
- const anchorLength = url.hash.length
- if (anchorLength < 2) {
- return url.href
- } else {
- return url.href.slice(0, -anchorLength)
- }
+ return getRequestURL(url)
}
export function urlsAreEqual(left: string, right: string) {
diff --git a/src/core/view.ts b/src/core/view.ts
index 3b5fd46c9..adb1ac8be 100644
--- a/src/core/view.ts
+++ b/src/core/view.ts
@@ -25,6 +25,7 @@ export abstract class View = Snapshot = Snapshot
-
diff --git a/src/tests/helpers/functional_test_case.ts b/src/tests/helpers/functional_test_case.ts
index 6a8f645f2..b478a8fcf 100644
--- a/src/tests/helpers/functional_test_case.ts
+++ b/src/tests/helpers/functional_test_case.ts
@@ -45,6 +45,10 @@ export class FunctionalTestCase extends InternTestCase {
return this.evaluate(element => element.scrollIntoView(), element)
}
+ async pressTab(): Promise {
+ return this.remote.getActiveElement().then(activeElement => activeElement.type(('\uE004'))) // TAB
+ }
+
async outerHTMLForSelector(selector: string): Promise {
const element = await this.remote.findByCssSelector(selector)
return this.evaluate(element => element.outerHTML, element)
@@ -98,6 +102,10 @@ export class FunctionalTestCase extends InternTestCase {
return this.evaluate(() => document.body as any)
}
+ async getActiveElement() {
+ return await this.remote.getActiveElement()
+ }
+
get location(): Promise {
return this.evaluate(() => location.toString())
}