@hotwired/turbo 7.1.0-rc.1 → 7.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/turbo.es2017-esm.js +39 -41
- package/dist/turbo.es2017-umd.js +39 -41
- package/dist/types/core/drive/navigator.d.ts +0 -1
- package/dist/types/core/drive/page_view.d.ts +2 -2
- package/dist/types/core/drive/visit.d.ts +5 -3
- package/dist/types/core/frames/frame_controller.d.ts +1 -0
- package/dist/types/core/renderer.d.ts +2 -1
- package/dist/types/core/session.d.ts +0 -1
- package/dist/types/elements/frame_element.d.ts +1 -0
- package/dist/types/tests/functional/frame_tests.d.ts +9 -0
- package/package.json +1 -1
package/dist/turbo.es2017-esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.0.1
|
|
2
|
+
Turbo 7.1.0-rc.1
|
|
3
3
|
Copyright © 2021 Basecamp, LLC
|
|
4
4
|
*/
|
|
5
5
|
(function () {
|
|
@@ -1010,10 +1010,11 @@ function createPlaceholderForPermanentElement(permanentElement) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
1012
|
class Renderer {
|
|
1013
|
-
constructor(currentSnapshot, newSnapshot, isPreview) {
|
|
1013
|
+
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
|
1014
1014
|
this.currentSnapshot = currentSnapshot;
|
|
1015
1015
|
this.newSnapshot = newSnapshot;
|
|
1016
1016
|
this.isPreview = isPreview;
|
|
1017
|
+
this.willRender = willRender;
|
|
1017
1018
|
this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
|
|
1018
1019
|
}
|
|
1019
1020
|
get shouldRender() {
|
|
@@ -1389,8 +1390,9 @@ var VisitState;
|
|
|
1389
1390
|
})(VisitState || (VisitState = {}));
|
|
1390
1391
|
const defaultOptions = {
|
|
1391
1392
|
action: "advance",
|
|
1392
|
-
delegate: {},
|
|
1393
1393
|
historyChanged: false,
|
|
1394
|
+
visitCachedSnapshot: () => { },
|
|
1395
|
+
willRender: true,
|
|
1394
1396
|
};
|
|
1395
1397
|
var SystemStatusCode;
|
|
1396
1398
|
(function (SystemStatusCode) {
|
|
@@ -1410,14 +1412,16 @@ class Visit {
|
|
|
1410
1412
|
this.delegate = delegate;
|
|
1411
1413
|
this.location = location;
|
|
1412
1414
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1413
|
-
const { action, historyChanged, referrer, snapshotHTML, response,
|
|
1415
|
+
const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1414
1416
|
this.action = action;
|
|
1415
1417
|
this.historyChanged = historyChanged;
|
|
1416
1418
|
this.referrer = referrer;
|
|
1417
1419
|
this.snapshotHTML = snapshotHTML;
|
|
1418
1420
|
this.response = response;
|
|
1419
1421
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
1420
|
-
this.
|
|
1422
|
+
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
1423
|
+
this.willRender = willRender;
|
|
1424
|
+
this.scrolled = !willRender;
|
|
1421
1425
|
}
|
|
1422
1426
|
get adapter() {
|
|
1423
1427
|
return this.delegate.adapter;
|
|
@@ -1440,8 +1444,6 @@ class Visit {
|
|
|
1440
1444
|
this.state = VisitState.started;
|
|
1441
1445
|
this.adapter.visitStarted(this);
|
|
1442
1446
|
this.delegate.visitStarted(this);
|
|
1443
|
-
if (this.optionalDelegate.visitStarted)
|
|
1444
|
-
this.optionalDelegate.visitStarted(this);
|
|
1445
1447
|
}
|
|
1446
1448
|
}
|
|
1447
1449
|
cancel() {
|
|
@@ -1521,7 +1523,7 @@ class Visit {
|
|
|
1521
1523
|
if (this.view.renderPromise)
|
|
1522
1524
|
await this.view.renderPromise;
|
|
1523
1525
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1524
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
|
1526
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
|
1525
1527
|
this.adapter.visitRendered(this);
|
|
1526
1528
|
this.complete();
|
|
1527
1529
|
}
|
|
@@ -1561,7 +1563,7 @@ class Visit {
|
|
|
1561
1563
|
else {
|
|
1562
1564
|
if (this.view.renderPromise)
|
|
1563
1565
|
await this.view.renderPromise;
|
|
1564
|
-
await this.view.renderPage(snapshot, isPreview);
|
|
1566
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
|
1565
1567
|
this.adapter.visitRendered(this);
|
|
1566
1568
|
if (!isPreview) {
|
|
1567
1569
|
this.complete();
|
|
@@ -1672,15 +1674,13 @@ class Visit {
|
|
|
1672
1674
|
return !this.hasCachedSnapshot();
|
|
1673
1675
|
}
|
|
1674
1676
|
else {
|
|
1675
|
-
return
|
|
1677
|
+
return this.willRender;
|
|
1676
1678
|
}
|
|
1677
1679
|
}
|
|
1678
1680
|
cacheSnapshot() {
|
|
1679
1681
|
if (!this.snapshotCached) {
|
|
1680
|
-
this.view.cacheSnapshot();
|
|
1682
|
+
this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
|
|
1681
1683
|
this.snapshotCached = true;
|
|
1682
|
-
if (this.optionalDelegate.visitCachedSnapshot)
|
|
1683
|
-
this.optionalDelegate.visitCachedSnapshot(this);
|
|
1684
1684
|
}
|
|
1685
1685
|
}
|
|
1686
1686
|
async render(callback) {
|
|
@@ -2134,9 +2134,6 @@ class Navigator {
|
|
|
2134
2134
|
visitCompleted(visit) {
|
|
2135
2135
|
this.delegate.visitCompleted(visit);
|
|
2136
2136
|
}
|
|
2137
|
-
visitCachedSnapshot(visit) {
|
|
2138
|
-
this.delegate.visitCachedSnapshot(visit);
|
|
2139
|
-
}
|
|
2140
2137
|
locationWithActionIsSamePage(location, action) {
|
|
2141
2138
|
const anchor = getAnchor(location);
|
|
2142
2139
|
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
@@ -2350,7 +2347,9 @@ class PageRenderer extends Renderer {
|
|
|
2350
2347
|
this.mergeHead();
|
|
2351
2348
|
}
|
|
2352
2349
|
async render() {
|
|
2353
|
-
this.
|
|
2350
|
+
if (this.willRender) {
|
|
2351
|
+
this.replaceBody();
|
|
2352
|
+
}
|
|
2354
2353
|
}
|
|
2355
2354
|
finishRendering() {
|
|
2356
2355
|
super.finishRendering();
|
|
@@ -2488,8 +2487,8 @@ class PageView extends View {
|
|
|
2488
2487
|
this.snapshotCache = new SnapshotCache(10);
|
|
2489
2488
|
this.lastRenderedLocation = new URL(location.href);
|
|
2490
2489
|
}
|
|
2491
|
-
renderPage(snapshot, isPreview = false) {
|
|
2492
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
|
2490
|
+
renderPage(snapshot, isPreview = false, willRender = true) {
|
|
2491
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
|
2493
2492
|
return this.render(renderer);
|
|
2494
2493
|
}
|
|
2495
2494
|
renderError(snapshot) {
|
|
@@ -2504,7 +2503,9 @@ class PageView extends View {
|
|
|
2504
2503
|
this.delegate.viewWillCacheSnapshot();
|
|
2505
2504
|
const { snapshot, lastRenderedLocation: location } = this;
|
|
2506
2505
|
await nextEventLoopTick();
|
|
2507
|
-
|
|
2506
|
+
const cachedSnapshot = snapshot.clone();
|
|
2507
|
+
this.snapshotCache.put(location, cachedSnapshot);
|
|
2508
|
+
return cachedSnapshot;
|
|
2508
2509
|
}
|
|
2509
2510
|
}
|
|
2510
2511
|
getCachedSnapshotForLocation(location) {
|
|
@@ -2654,8 +2655,6 @@ class Session {
|
|
|
2654
2655
|
visitCompleted(visit) {
|
|
2655
2656
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
2656
2657
|
}
|
|
2657
|
-
visitCachedSnapshot(visit) {
|
|
2658
|
-
}
|
|
2659
2658
|
locationWithActionIsSamePage(location, action) {
|
|
2660
2659
|
return this.navigator.locationWithActionIsSamePage(location, action);
|
|
2661
2660
|
}
|
|
@@ -2846,6 +2845,7 @@ var Turbo = /*#__PURE__*/Object.freeze({
|
|
|
2846
2845
|
|
|
2847
2846
|
class FrameController {
|
|
2848
2847
|
constructor(element) {
|
|
2848
|
+
this.fetchResponseLoaded = (fetchResponse) => { };
|
|
2849
2849
|
this.currentFetchRequest = null;
|
|
2850
2850
|
this.resolveVisitPromise = () => { };
|
|
2851
2851
|
this.connected = false;
|
|
@@ -2915,7 +2915,7 @@ class FrameController {
|
|
|
2915
2915
|
}
|
|
2916
2916
|
}
|
|
2917
2917
|
async loadResponse(fetchResponse) {
|
|
2918
|
-
if (fetchResponse.redirected) {
|
|
2918
|
+
if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
|
|
2919
2919
|
this.sourceURL = fetchResponse.response.url;
|
|
2920
2920
|
}
|
|
2921
2921
|
try {
|
|
@@ -2923,18 +2923,22 @@ class FrameController {
|
|
|
2923
2923
|
if (html) {
|
|
2924
2924
|
const { body } = parseHTMLDocument(html);
|
|
2925
2925
|
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
|
2926
|
-
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
|
2926
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
|
|
2927
2927
|
if (this.view.renderPromise)
|
|
2928
2928
|
await this.view.renderPromise;
|
|
2929
2929
|
await this.view.render(renderer);
|
|
2930
2930
|
session.frameRendered(fetchResponse, this.element);
|
|
2931
2931
|
session.frameLoaded(this.element);
|
|
2932
|
+
this.fetchResponseLoaded(fetchResponse);
|
|
2932
2933
|
}
|
|
2933
2934
|
}
|
|
2934
2935
|
catch (error) {
|
|
2935
2936
|
console.error(error);
|
|
2936
2937
|
this.view.invalidate();
|
|
2937
2938
|
}
|
|
2939
|
+
finally {
|
|
2940
|
+
this.fetchResponseLoaded = () => { };
|
|
2941
|
+
}
|
|
2938
2942
|
}
|
|
2939
2943
|
elementAppearedInViewport(element) {
|
|
2940
2944
|
this.loadSourceURL();
|
|
@@ -3035,17 +3039,15 @@ class FrameController {
|
|
|
3035
3039
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3036
3040
|
const action = getAttribute("data-turbo-action", submitter, element, frame);
|
|
3037
3041
|
if (isAction(action)) {
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
if (target instanceof FrameElement && target.src) {
|
|
3042
|
+
const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
|
|
3043
|
+
frame.delegate.fetchResponseLoaded = (fetchResponse) => {
|
|
3044
|
+
if (frame.src) {
|
|
3042
3045
|
const { statusCode, redirected } = fetchResponse;
|
|
3043
|
-
const responseHTML =
|
|
3046
|
+
const responseHTML = frame.ownerDocument.documentElement.outerHTML;
|
|
3044
3047
|
const response = { statusCode, redirected, responseHTML };
|
|
3045
|
-
session.visit(
|
|
3048
|
+
session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
|
|
3046
3049
|
}
|
|
3047
3050
|
};
|
|
3048
|
-
frame.addEventListener("turbo:frame-render", proposeVisit, { once: true });
|
|
3049
3051
|
}
|
|
3050
3052
|
}
|
|
3051
3053
|
findFrameElement(element, submitter) {
|
|
@@ -3145,19 +3147,14 @@ class FrameController {
|
|
|
3145
3147
|
}
|
|
3146
3148
|
class SnapshotSubstitution {
|
|
3147
3149
|
constructor(element) {
|
|
3150
|
+
this.visitCachedSnapshot = ({ element }) => {
|
|
3151
|
+
var _a;
|
|
3152
|
+
const { id, clone } = this;
|
|
3153
|
+
(_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3154
|
+
};
|
|
3148
3155
|
this.clone = element.cloneNode(true);
|
|
3149
3156
|
this.id = element.id;
|
|
3150
3157
|
}
|
|
3151
|
-
visitStarted(visit) {
|
|
3152
|
-
this.snapshot = visit.view.snapshot;
|
|
3153
|
-
}
|
|
3154
|
-
visitCachedSnapshot() {
|
|
3155
|
-
var _a;
|
|
3156
|
-
const { snapshot, id, clone } = this;
|
|
3157
|
-
if (snapshot) {
|
|
3158
|
-
(_a = snapshot.element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
3158
|
}
|
|
3162
3159
|
function getFrameElementById(id) {
|
|
3163
3160
|
if (id != null) {
|
|
@@ -3178,6 +3175,7 @@ function activateElement(element, currentURL) {
|
|
|
3178
3175
|
}
|
|
3179
3176
|
if (element instanceof FrameElement) {
|
|
3180
3177
|
element.connectedCallback();
|
|
3178
|
+
element.disconnectedCallback();
|
|
3181
3179
|
return element;
|
|
3182
3180
|
}
|
|
3183
3181
|
}
|
package/dist/turbo.es2017-umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
Turbo 7.0.1
|
|
2
|
+
Turbo 7.1.0-rc.1
|
|
3
3
|
Copyright © 2021 Basecamp, LLC
|
|
4
4
|
*/
|
|
5
5
|
(function (global, factory) {
|
|
@@ -1016,10 +1016,11 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
1018
1018
|
class Renderer {
|
|
1019
|
-
constructor(currentSnapshot, newSnapshot, isPreview) {
|
|
1019
|
+
constructor(currentSnapshot, newSnapshot, isPreview, willRender = true) {
|
|
1020
1020
|
this.currentSnapshot = currentSnapshot;
|
|
1021
1021
|
this.newSnapshot = newSnapshot;
|
|
1022
1022
|
this.isPreview = isPreview;
|
|
1023
|
+
this.willRender = willRender;
|
|
1023
1024
|
this.promise = new Promise((resolve, reject) => this.resolvingFunctions = { resolve, reject });
|
|
1024
1025
|
}
|
|
1025
1026
|
get shouldRender() {
|
|
@@ -1395,8 +1396,9 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1395
1396
|
})(VisitState || (VisitState = {}));
|
|
1396
1397
|
const defaultOptions = {
|
|
1397
1398
|
action: "advance",
|
|
1398
|
-
delegate: {},
|
|
1399
1399
|
historyChanged: false,
|
|
1400
|
+
visitCachedSnapshot: () => { },
|
|
1401
|
+
willRender: true,
|
|
1400
1402
|
};
|
|
1401
1403
|
var SystemStatusCode;
|
|
1402
1404
|
(function (SystemStatusCode) {
|
|
@@ -1416,14 +1418,16 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1416
1418
|
this.delegate = delegate;
|
|
1417
1419
|
this.location = location;
|
|
1418
1420
|
this.restorationIdentifier = restorationIdentifier || uuid();
|
|
1419
|
-
const { action, historyChanged, referrer, snapshotHTML, response,
|
|
1421
|
+
const { action, historyChanged, referrer, snapshotHTML, response, visitCachedSnapshot, willRender } = Object.assign(Object.assign({}, defaultOptions), options);
|
|
1420
1422
|
this.action = action;
|
|
1421
1423
|
this.historyChanged = historyChanged;
|
|
1422
1424
|
this.referrer = referrer;
|
|
1423
1425
|
this.snapshotHTML = snapshotHTML;
|
|
1424
1426
|
this.response = response;
|
|
1425
1427
|
this.isSamePage = this.delegate.locationWithActionIsSamePage(this.location, this.action);
|
|
1426
|
-
this.
|
|
1428
|
+
this.visitCachedSnapshot = visitCachedSnapshot;
|
|
1429
|
+
this.willRender = willRender;
|
|
1430
|
+
this.scrolled = !willRender;
|
|
1427
1431
|
}
|
|
1428
1432
|
get adapter() {
|
|
1429
1433
|
return this.delegate.adapter;
|
|
@@ -1446,8 +1450,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1446
1450
|
this.state = VisitState.started;
|
|
1447
1451
|
this.adapter.visitStarted(this);
|
|
1448
1452
|
this.delegate.visitStarted(this);
|
|
1449
|
-
if (this.optionalDelegate.visitStarted)
|
|
1450
|
-
this.optionalDelegate.visitStarted(this);
|
|
1451
1453
|
}
|
|
1452
1454
|
}
|
|
1453
1455
|
cancel() {
|
|
@@ -1527,7 +1529,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1527
1529
|
if (this.view.renderPromise)
|
|
1528
1530
|
await this.view.renderPromise;
|
|
1529
1531
|
if (isSuccessful(statusCode) && responseHTML != null) {
|
|
1530
|
-
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
|
|
1532
|
+
await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML), false, this.willRender);
|
|
1531
1533
|
this.adapter.visitRendered(this);
|
|
1532
1534
|
this.complete();
|
|
1533
1535
|
}
|
|
@@ -1567,7 +1569,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1567
1569
|
else {
|
|
1568
1570
|
if (this.view.renderPromise)
|
|
1569
1571
|
await this.view.renderPromise;
|
|
1570
|
-
await this.view.renderPage(snapshot, isPreview);
|
|
1572
|
+
await this.view.renderPage(snapshot, isPreview, this.willRender);
|
|
1571
1573
|
this.adapter.visitRendered(this);
|
|
1572
1574
|
if (!isPreview) {
|
|
1573
1575
|
this.complete();
|
|
@@ -1678,15 +1680,13 @@ Copyright © 2021 Basecamp, LLC
|
|
|
1678
1680
|
return !this.hasCachedSnapshot();
|
|
1679
1681
|
}
|
|
1680
1682
|
else {
|
|
1681
|
-
return
|
|
1683
|
+
return this.willRender;
|
|
1682
1684
|
}
|
|
1683
1685
|
}
|
|
1684
1686
|
cacheSnapshot() {
|
|
1685
1687
|
if (!this.snapshotCached) {
|
|
1686
|
-
this.view.cacheSnapshot();
|
|
1688
|
+
this.view.cacheSnapshot().then(snapshot => snapshot && this.visitCachedSnapshot(snapshot));
|
|
1687
1689
|
this.snapshotCached = true;
|
|
1688
|
-
if (this.optionalDelegate.visitCachedSnapshot)
|
|
1689
|
-
this.optionalDelegate.visitCachedSnapshot(this);
|
|
1690
1690
|
}
|
|
1691
1691
|
}
|
|
1692
1692
|
async render(callback) {
|
|
@@ -2140,9 +2140,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2140
2140
|
visitCompleted(visit) {
|
|
2141
2141
|
this.delegate.visitCompleted(visit);
|
|
2142
2142
|
}
|
|
2143
|
-
visitCachedSnapshot(visit) {
|
|
2144
|
-
this.delegate.visitCachedSnapshot(visit);
|
|
2145
|
-
}
|
|
2146
2143
|
locationWithActionIsSamePage(location, action) {
|
|
2147
2144
|
const anchor = getAnchor(location);
|
|
2148
2145
|
const currentAnchor = getAnchor(this.view.lastRenderedLocation);
|
|
@@ -2356,7 +2353,9 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2356
2353
|
this.mergeHead();
|
|
2357
2354
|
}
|
|
2358
2355
|
async render() {
|
|
2359
|
-
this.
|
|
2356
|
+
if (this.willRender) {
|
|
2357
|
+
this.replaceBody();
|
|
2358
|
+
}
|
|
2360
2359
|
}
|
|
2361
2360
|
finishRendering() {
|
|
2362
2361
|
super.finishRendering();
|
|
@@ -2494,8 +2493,8 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2494
2493
|
this.snapshotCache = new SnapshotCache(10);
|
|
2495
2494
|
this.lastRenderedLocation = new URL(location.href);
|
|
2496
2495
|
}
|
|
2497
|
-
renderPage(snapshot, isPreview = false) {
|
|
2498
|
-
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
|
|
2496
|
+
renderPage(snapshot, isPreview = false, willRender = true) {
|
|
2497
|
+
const renderer = new PageRenderer(this.snapshot, snapshot, isPreview, willRender);
|
|
2499
2498
|
return this.render(renderer);
|
|
2500
2499
|
}
|
|
2501
2500
|
renderError(snapshot) {
|
|
@@ -2510,7 +2509,9 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2510
2509
|
this.delegate.viewWillCacheSnapshot();
|
|
2511
2510
|
const { snapshot, lastRenderedLocation: location } = this;
|
|
2512
2511
|
await nextEventLoopTick();
|
|
2513
|
-
|
|
2512
|
+
const cachedSnapshot = snapshot.clone();
|
|
2513
|
+
this.snapshotCache.put(location, cachedSnapshot);
|
|
2514
|
+
return cachedSnapshot;
|
|
2514
2515
|
}
|
|
2515
2516
|
}
|
|
2516
2517
|
getCachedSnapshotForLocation(location) {
|
|
@@ -2660,8 +2661,6 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2660
2661
|
visitCompleted(visit) {
|
|
2661
2662
|
this.notifyApplicationAfterPageLoad(visit.getTimingMetrics());
|
|
2662
2663
|
}
|
|
2663
|
-
visitCachedSnapshot(visit) {
|
|
2664
|
-
}
|
|
2665
2664
|
locationWithActionIsSamePage(location, action) {
|
|
2666
2665
|
return this.navigator.locationWithActionIsSamePage(location, action);
|
|
2667
2666
|
}
|
|
@@ -2852,6 +2851,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2852
2851
|
|
|
2853
2852
|
class FrameController {
|
|
2854
2853
|
constructor(element) {
|
|
2854
|
+
this.fetchResponseLoaded = (fetchResponse) => { };
|
|
2855
2855
|
this.currentFetchRequest = null;
|
|
2856
2856
|
this.resolveVisitPromise = () => { };
|
|
2857
2857
|
this.connected = false;
|
|
@@ -2921,7 +2921,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2921
2921
|
}
|
|
2922
2922
|
}
|
|
2923
2923
|
async loadResponse(fetchResponse) {
|
|
2924
|
-
if (fetchResponse.redirected) {
|
|
2924
|
+
if (fetchResponse.redirected || (fetchResponse.succeeded && fetchResponse.isHTML)) {
|
|
2925
2925
|
this.sourceURL = fetchResponse.response.url;
|
|
2926
2926
|
}
|
|
2927
2927
|
try {
|
|
@@ -2929,18 +2929,22 @@ Copyright © 2021 Basecamp, LLC
|
|
|
2929
2929
|
if (html) {
|
|
2930
2930
|
const { body } = parseHTMLDocument(html);
|
|
2931
2931
|
const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
|
|
2932
|
-
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
|
|
2932
|
+
const renderer = new FrameRenderer(this.view.snapshot, snapshot, false, false);
|
|
2933
2933
|
if (this.view.renderPromise)
|
|
2934
2934
|
await this.view.renderPromise;
|
|
2935
2935
|
await this.view.render(renderer);
|
|
2936
2936
|
session.frameRendered(fetchResponse, this.element);
|
|
2937
2937
|
session.frameLoaded(this.element);
|
|
2938
|
+
this.fetchResponseLoaded(fetchResponse);
|
|
2938
2939
|
}
|
|
2939
2940
|
}
|
|
2940
2941
|
catch (error) {
|
|
2941
2942
|
console.error(error);
|
|
2942
2943
|
this.view.invalidate();
|
|
2943
2944
|
}
|
|
2945
|
+
finally {
|
|
2946
|
+
this.fetchResponseLoaded = () => { };
|
|
2947
|
+
}
|
|
2944
2948
|
}
|
|
2945
2949
|
elementAppearedInViewport(element) {
|
|
2946
2950
|
this.loadSourceURL();
|
|
@@ -3041,17 +3045,15 @@ Copyright © 2021 Basecamp, LLC
|
|
|
3041
3045
|
proposeVisitIfNavigatedWithAction(frame, element, submitter) {
|
|
3042
3046
|
const action = getAttribute("data-turbo-action", submitter, element, frame);
|
|
3043
3047
|
if (isAction(action)) {
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
if (target instanceof FrameElement && target.src) {
|
|
3048
|
+
const { visitCachedSnapshot } = new SnapshotSubstitution(frame);
|
|
3049
|
+
frame.delegate.fetchResponseLoaded = (fetchResponse) => {
|
|
3050
|
+
if (frame.src) {
|
|
3048
3051
|
const { statusCode, redirected } = fetchResponse;
|
|
3049
|
-
const responseHTML =
|
|
3052
|
+
const responseHTML = frame.ownerDocument.documentElement.outerHTML;
|
|
3050
3053
|
const response = { statusCode, redirected, responseHTML };
|
|
3051
|
-
session.visit(
|
|
3054
|
+
session.visit(frame.src, { action, response, visitCachedSnapshot, willRender: false });
|
|
3052
3055
|
}
|
|
3053
3056
|
};
|
|
3054
|
-
frame.addEventListener("turbo:frame-render", proposeVisit, { once: true });
|
|
3055
3057
|
}
|
|
3056
3058
|
}
|
|
3057
3059
|
findFrameElement(element, submitter) {
|
|
@@ -3151,19 +3153,14 @@ Copyright © 2021 Basecamp, LLC
|
|
|
3151
3153
|
}
|
|
3152
3154
|
class SnapshotSubstitution {
|
|
3153
3155
|
constructor(element) {
|
|
3156
|
+
this.visitCachedSnapshot = ({ element }) => {
|
|
3157
|
+
var _a;
|
|
3158
|
+
const { id, clone } = this;
|
|
3159
|
+
(_a = element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3160
|
+
};
|
|
3154
3161
|
this.clone = element.cloneNode(true);
|
|
3155
3162
|
this.id = element.id;
|
|
3156
3163
|
}
|
|
3157
|
-
visitStarted(visit) {
|
|
3158
|
-
this.snapshot = visit.view.snapshot;
|
|
3159
|
-
}
|
|
3160
|
-
visitCachedSnapshot() {
|
|
3161
|
-
var _a;
|
|
3162
|
-
const { snapshot, id, clone } = this;
|
|
3163
|
-
if (snapshot) {
|
|
3164
|
-
(_a = snapshot.element.querySelector("#" + id)) === null || _a === void 0 ? void 0 : _a.replaceWith(clone);
|
|
3165
|
-
}
|
|
3166
|
-
}
|
|
3167
3164
|
}
|
|
3168
3165
|
function getFrameElementById(id) {
|
|
3169
3166
|
if (id != null) {
|
|
@@ -3184,6 +3181,7 @@ Copyright © 2021 Basecamp, LLC
|
|
|
3184
3181
|
}
|
|
3185
3182
|
if (element instanceof FrameElement) {
|
|
3186
3183
|
element.connectedCallback();
|
|
3184
|
+
element.disconnectedCallback();
|
|
3187
3185
|
return element;
|
|
3188
3186
|
}
|
|
3189
3187
|
}
|
|
@@ -27,7 +27,6 @@ export declare class Navigator {
|
|
|
27
27
|
formSubmissionFinished(formSubmission: FormSubmission): void;
|
|
28
28
|
visitStarted(visit: Visit): void;
|
|
29
29
|
visitCompleted(visit: Visit): void;
|
|
30
|
-
visitCachedSnapshot(visit: Visit): void;
|
|
31
30
|
locationWithActionIsSamePage(location: URL, action?: Action): boolean;
|
|
32
31
|
visitScrolledToSamePageLocation(oldURL: URL, newURL: URL): void;
|
|
33
32
|
get location(): URL;
|
|
@@ -10,10 +10,10 @@ declare type PageViewRenderer = PageRenderer | ErrorRenderer;
|
|
|
10
10
|
export declare class PageView extends View<Element, PageSnapshot, PageViewRenderer, PageViewDelegate> {
|
|
11
11
|
readonly snapshotCache: SnapshotCache;
|
|
12
12
|
lastRenderedLocation: URL;
|
|
13
|
-
renderPage(snapshot: PageSnapshot, isPreview?: boolean): Promise<void>;
|
|
13
|
+
renderPage(snapshot: PageSnapshot, isPreview?: boolean, willRender?: boolean): Promise<void>;
|
|
14
14
|
renderError(snapshot: PageSnapshot): Promise<void>;
|
|
15
15
|
clearSnapshotCache(): void;
|
|
16
|
-
cacheSnapshot(): Promise<
|
|
16
|
+
cacheSnapshot(): Promise<PageSnapshot | undefined>;
|
|
17
17
|
getCachedSnapshotForLocation(location: URL): PageSnapshot | undefined;
|
|
18
18
|
get snapshot(): PageSnapshot;
|
|
19
19
|
get shouldCacheSnapshot(): boolean;
|
|
@@ -2,6 +2,7 @@ import { Adapter } from "../native/adapter";
|
|
|
2
2
|
import { FetchRequest, FetchRequestDelegate } from "../../http/fetch_request";
|
|
3
3
|
import { FetchResponse } from "../../http/fetch_response";
|
|
4
4
|
import { History } from "./history";
|
|
5
|
+
import { Snapshot } from "../snapshot";
|
|
5
6
|
import { PageSnapshot } from "./page_snapshot";
|
|
6
7
|
import { Action } from "../types";
|
|
7
8
|
import { PageView } from "./page_view";
|
|
@@ -11,7 +12,6 @@ export interface VisitDelegate {
|
|
|
11
12
|
readonly view: PageView;
|
|
12
13
|
visitStarted(visit: Visit): void;
|
|
13
14
|
visitCompleted(visit: Visit): void;
|
|
14
|
-
visitCachedSnapshot(visit: Visit): void;
|
|
15
15
|
locationWithActionIsSamePage(location: URL, action: Action): boolean;
|
|
16
16
|
visitScrolledToSamePageLocation(oldURL: URL, newURL: URL): void;
|
|
17
17
|
}
|
|
@@ -33,11 +33,12 @@ export declare enum VisitState {
|
|
|
33
33
|
}
|
|
34
34
|
export declare type VisitOptions = {
|
|
35
35
|
action: Action;
|
|
36
|
-
delegate: Partial<VisitDelegate>;
|
|
37
36
|
historyChanged: boolean;
|
|
38
37
|
referrer?: URL;
|
|
39
38
|
snapshotHTML?: string;
|
|
40
39
|
response?: VisitResponse;
|
|
40
|
+
visitCachedSnapshot(snapshot: Snapshot): void;
|
|
41
|
+
willRender: boolean;
|
|
41
42
|
};
|
|
42
43
|
export declare type VisitResponse = {
|
|
43
44
|
statusCode: number;
|
|
@@ -56,7 +57,8 @@ export declare class Visit implements FetchRequestDelegate {
|
|
|
56
57
|
readonly action: Action;
|
|
57
58
|
readonly referrer?: URL;
|
|
58
59
|
readonly timingMetrics: TimingMetrics;
|
|
59
|
-
readonly
|
|
60
|
+
readonly visitCachedSnapshot: (snapshot: Snapshot) => void;
|
|
61
|
+
readonly willRender: boolean;
|
|
60
62
|
followedRedirect: boolean;
|
|
61
63
|
frame?: number;
|
|
62
64
|
historyChanged: boolean;
|
|
@@ -16,6 +16,7 @@ export declare class FrameController implements AppearanceObserverDelegate, Fetc
|
|
|
16
16
|
readonly formInterceptor: FormInterceptor;
|
|
17
17
|
currentURL?: string | null;
|
|
18
18
|
formSubmission?: FormSubmission;
|
|
19
|
+
fetchResponseLoaded: (fetchResponse: FetchResponse) => void;
|
|
19
20
|
private currentFetchRequest;
|
|
20
21
|
private resolveVisitPromise;
|
|
21
22
|
private connected;
|
|
@@ -3,9 +3,10 @@ export declare abstract class Renderer<E extends Element, S extends Snapshot<E>
|
|
|
3
3
|
readonly currentSnapshot: S;
|
|
4
4
|
readonly newSnapshot: S;
|
|
5
5
|
readonly isPreview: boolean;
|
|
6
|
+
readonly willRender: boolean;
|
|
6
7
|
readonly promise: Promise<void>;
|
|
7
8
|
private resolvingFunctions?;
|
|
8
|
-
constructor(currentSnapshot: S, newSnapshot: S, isPreview: boolean);
|
|
9
|
+
constructor(currentSnapshot: S, newSnapshot: S, isPreview: boolean, willRender?: boolean);
|
|
9
10
|
get shouldRender(): boolean;
|
|
10
11
|
prepareToRender(): void;
|
|
11
12
|
abstract render(): Promise<void>;
|
|
@@ -54,7 +54,6 @@ export declare class Session implements FormSubmitObserverDelegate, HistoryDeleg
|
|
|
54
54
|
visitProposedToLocation(location: URL, options: Partial<VisitOptions>): void;
|
|
55
55
|
visitStarted(visit: Visit): void;
|
|
56
56
|
visitCompleted(visit: Visit): void;
|
|
57
|
-
visitCachedSnapshot(visit: Visit): void;
|
|
58
57
|
locationWithActionIsSamePage(location: URL, action?: Action): boolean;
|
|
59
58
|
visitScrolledToSamePageLocation(oldURL: URL, newURL: URL): void;
|
|
60
59
|
willSubmitForm(form: HTMLFormElement, submitter?: HTMLElement): boolean;
|
|
@@ -12,6 +12,7 @@ export interface FrameElementDelegate {
|
|
|
12
12
|
formSubmissionIntercepted(element: HTMLFormElement, submitter?: HTMLElement): void;
|
|
13
13
|
linkClickIntercepted(element: Element, url: string): void;
|
|
14
14
|
loadResponse(response: FetchResponse): void;
|
|
15
|
+
fetchResponseLoaded: (fetchResponse: FetchResponse) => void;
|
|
15
16
|
isLoading: boolean;
|
|
16
17
|
}
|
|
17
18
|
export declare class FrameElement extends HTMLElement {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TurboDriveTestCase } from "../helpers/turbo_drive_test_case";
|
|
2
2
|
export declare class FrameTests extends TurboDriveTestCase {
|
|
3
3
|
setup(): Promise<void>;
|
|
4
|
+
"test navigating a frame a second time does not leak event listeners"(): Promise<void>;
|
|
4
5
|
"test following a link preserves the current <turbo-frame> element's attributes"(): Promise<void>;
|
|
5
6
|
"test a frame whose src references itself does not infinitely loop"(): Promise<void>;
|
|
6
7
|
"test following a link driving a frame toggles the [aria-busy=true] attribute"(): Promise<void>;
|
|
@@ -28,16 +29,24 @@ export declare class FrameTests extends TurboDriveTestCase {
|
|
|
28
29
|
"test an inner/outer form reloads frame on every submit"(): Promise<void>;
|
|
29
30
|
"test reconnecting after following a link does not reload the frame"(): Promise<void>;
|
|
30
31
|
"test navigating pushing URL state from a frame navigation fires events"(): Promise<void>;
|
|
32
|
+
"test navigating a frame with a form[method=get] that does not redirect still updates the [src]"(): Promise<void>;
|
|
31
33
|
"test navigating turbo-frame[data-turbo-action=advance] from within pushes URL state"(): Promise<void>;
|
|
34
|
+
"test navigating turbo-frame[data-turbo-action=advance] to the same URL clears the [aria-busy] and [data-turbo-preview] state"(): Promise<void>;
|
|
35
|
+
"test navigating a turbo-frame with an a[data-turbo-action=advance] preserves page state"(): Promise<void>;
|
|
36
|
+
"test a turbo-frame that has been driven by a[data-turbo-action] can be navigated normally"(): Promise<void>;
|
|
32
37
|
"test navigating turbo-frame from within with a[data-turbo-action=advance] pushes URL state"(): Promise<void>;
|
|
33
38
|
"test navigating frame with a[data-turbo-action=advance] pushes URL state"(): Promise<void>;
|
|
34
39
|
"test navigating frame with form[method=get][data-turbo-action=advance] pushes URL state"(): Promise<void>;
|
|
40
|
+
"test navigating frame with form[method=get][data-turbo-action=advance] to the same URL clears the [aria-busy] and [data-turbo-preview] state"(): Promise<void>;
|
|
35
41
|
"test navigating frame with form[method=post][data-turbo-action=advance] pushes URL state"(): Promise<void>;
|
|
42
|
+
"test navigating frame with form[method=post][data-turbo-action=advance] to the same URL clears the [aria-busy] and [data-turbo-preview] state"(): Promise<void>;
|
|
36
43
|
"test navigating frame with button[data-turbo-action=advance] pushes URL state"(): Promise<void>;
|
|
37
44
|
"test navigating back after pushing URL state from a turbo-frame[data-turbo-action=advance] restores the frames previous contents"(): Promise<void>;
|
|
38
45
|
"test navigating back then forward after pushing URL state from a turbo-frame[data-turbo-action=advance] restores the frames next contents"(): Promise<void>;
|
|
39
46
|
"test turbo:before-fetch-request fires on the frame element"(): Promise<void>;
|
|
40
47
|
"test turbo:before-fetch-response fires on the frame element"(): Promise<void>;
|
|
48
|
+
withoutChangingEventListenersCount(callback: () => void): Promise<void>;
|
|
49
|
+
fillInSelector(selector: string, value: string): Promise<void>;
|
|
41
50
|
get frameScriptEvaluationCount(): Promise<number | undefined>;
|
|
42
51
|
}
|
|
43
52
|
declare global {
|
package/package.json
CHANGED