turbo-rails 0.5.8 → 0.5.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4482a7f7bf938ab7f2a3d7d9e387d632a64dca82605b8bf20cd8e2dce654488
4
- data.tar.gz: 95b3c7e4b3d4d55205fee6c8b8f6bcba6acae2c13e9fa06dd90e5fb018854dde
3
+ metadata.gz: 025ba92afa27783a938ec698ff465685559d71698ab3023b091a54d14f0d6dd1
4
+ data.tar.gz: ae377ccfdb57c9f9f2f862dc81e5863a5bc614002bbdfe39547cb1ede2aa797a
5
5
  SHA512:
6
- metadata.gz: bd2ea89d24c93904c1f8c701d0db808b85b9003c48b1596d57caed21512c87544565741709ed7532ab60a70cf7aa389f61c1e3420e36b9e7539126d786981528
7
- data.tar.gz: b1d74905d811d787da2ee3afee23eb7ac8723f7bb93b09857b8e0deeb047407c3b4ce5ce8737ec2895e0bcb352480095b06a0b0c0b9f2e99aae748106291fd70
6
+ metadata.gz: a7f03c2b76b07d65f64b3170b9f9ed6c1a9c4429c9f7eee3abf8bffbdbed8a320806f239df725c5aad37c826d2171ec2abc0c7787d42f929e2741584fc4d84e3
7
+ data.tar.gz: b2566bd6203b982d5b3cad1c852517a41b3a11ca8c8f097cac3cf7ff45f8d83db3eab3337807a80299cf61fc21f58990275f2e5dc92af75b03b167bf04f05ecc
@@ -132,82 +132,59 @@ function frameLoadingStyleFromString(style) {
132
132
  }
133
133
  }
134
134
 
135
- class Location {
136
- constructor(url) {
137
- const linkWithAnchor = document.createElement("a");
138
- linkWithAnchor.href = url;
139
- this.absoluteURL = linkWithAnchor.href;
140
- const anchorLength = linkWithAnchor.hash.length;
141
- if (anchorLength < 2) {
142
- this.requestURL = this.absoluteURL;
143
- } else {
144
- this.requestURL = this.absoluteURL.slice(0, -anchorLength);
145
- this.anchor = linkWithAnchor.hash.slice(1);
146
- }
147
- }
148
- static get currentLocation() {
149
- return this.wrap(window.location.toString());
150
- }
151
- static wrap(locatable) {
152
- if (typeof locatable == "string") {
153
- return new this(locatable);
154
- } else if (locatable != null) {
155
- return locatable;
156
- }
157
- }
158
- getOrigin() {
159
- return this.absoluteURL.split("/", 3).join("/");
160
- }
161
- getPath() {
162
- return (this.requestURL.match(/\/\/[^/]*(\/[^?;]*)/) || [])[1] || "/";
163
- }
164
- getPathComponents() {
165
- return this.getPath().split("/").slice(1);
166
- }
167
- getLastPathComponent() {
168
- return this.getPathComponents().slice(-1)[0];
169
- }
170
- getExtension() {
171
- return (this.getLastPathComponent().match(/\.[^.]*$/) || [])[0] || "";
172
- }
173
- isHTML() {
174
- return !!this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/);
175
- }
176
- isPrefixedBy(location) {
177
- const prefixURL = getPrefixURL(location);
178
- return this.isEqualTo(location) || stringStartsWith(this.absoluteURL, prefixURL);
179
- }
180
- isEqualTo(location) {
181
- return location && this.absoluteURL === location.absoluteURL;
182
- }
183
- toCacheKey() {
184
- return this.requestURL;
185
- }
186
- toJSON() {
187
- return this.absoluteURL;
188
- }
189
- toString() {
190
- return this.absoluteURL;
135
+ function expandURL(locatable) {
136
+ const anchor = document.createElement("a");
137
+ anchor.href = locatable.toString();
138
+ return new URL(anchor.href);
139
+ }
140
+
141
+ function getAnchor(url) {
142
+ let anchorMatch;
143
+ if (url.hash) {
144
+ return url.hash.slice(1);
145
+ } else if (anchorMatch = url.href.match(/#(.*)$/)) {
146
+ return anchorMatch[1];
147
+ } else {
148
+ return "";
191
149
  }
192
- valueOf() {
193
- return this.absoluteURL;
150
+ }
151
+
152
+ function getExtension(url) {
153
+ return (getLastPathComponent(url).match(/\.[^.]*$/) || [])[0] || "";
154
+ }
155
+
156
+ function isHTML(url) {
157
+ return !!getExtension(url).match(/^(?:|\.(?:htm|html|xhtml))$/);
158
+ }
159
+
160
+ function isPrefixedBy(baseURL, url) {
161
+ const prefix = getPrefix(url);
162
+ return baseURL.href === expandURL(prefix).href || baseURL.href.startsWith(prefix);
163
+ }
164
+
165
+ function toCacheKey(url) {
166
+ const anchorLength = url.hash.length;
167
+ if (anchorLength < 2) {
168
+ return url.href;
169
+ } else {
170
+ return url.href.slice(0, -anchorLength);
194
171
  }
195
172
  }
196
173
 
197
- function getPrefixURL(location) {
198
- return addTrailingSlash(location.getOrigin() + location.getPath());
174
+ function getPathComponents(url) {
175
+ return url.pathname.split("/").slice(1);
199
176
  }
200
177
 
201
- function addTrailingSlash(url) {
202
- return stringEndsWith(url, "/") ? url : url + "/";
178
+ function getLastPathComponent(url) {
179
+ return getPathComponents(url).slice(-1)[0];
203
180
  }
204
181
 
205
- function stringStartsWith(string, prefix) {
206
- return string.slice(0, prefix.length) === prefix;
182
+ function getPrefix(url) {
183
+ return addTrailingSlash(url.origin + url.pathname);
207
184
  }
208
185
 
209
- function stringEndsWith(string, suffix) {
210
- return string.slice(-suffix.length) === suffix;
186
+ function addTrailingSlash(value) {
187
+ return value.endsWith("/") ? value : value + "/";
211
188
  }
212
189
 
213
190
  class FetchResponse {
@@ -230,7 +207,7 @@ class FetchResponse {
230
207
  return this.response.redirected;
231
208
  }
232
209
  get location() {
233
- return Location.wrap(this.response.url);
210
+ return expandURL(this.response.url);
234
211
  }
235
212
  get isHTML() {
236
213
  return this.contentType && this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/);
@@ -270,10 +247,18 @@ function nextAnimationFrame() {
270
247
  return new Promise((resolve => requestAnimationFrame((() => resolve()))));
271
248
  }
272
249
 
250
+ function nextEventLoopTick() {
251
+ return new Promise((resolve => setTimeout((() => resolve()), 0)));
252
+ }
253
+
273
254
  function nextMicrotask() {
274
255
  return Promise.resolve();
275
256
  }
276
257
 
258
+ function parseHTMLDocument(html = "") {
259
+ return (new DOMParser).parseFromString(html, "text/html");
260
+ }
261
+
277
262
  function unindent(strings, ...values) {
278
263
  const lines = interpolate(strings, values).replace(/^\n/, "").split("\n");
279
264
  const match = lines[0].match(/^\s+/);
@@ -334,27 +319,22 @@ function fetchMethodFromString(method) {
334
319
  }
335
320
 
336
321
  class FetchRequest {
337
- constructor(delegate, method, location, body) {
322
+ constructor(delegate, method, location, body = new URLSearchParams) {
338
323
  this.abortController = new AbortController;
339
324
  this.delegate = delegate;
340
325
  this.method = method;
341
- this.location = location;
342
- this.body = body;
343
- }
344
- get url() {
345
- const url = this.location.absoluteURL;
346
- const query = this.params.toString();
347
- if (this.isIdempotent && query.length) {
348
- return [ url, query ].join(url.includes("?") ? "&" : "?");
326
+ if (this.isIdempotent) {
327
+ this.url = mergeFormDataEntries(location, [ ...body.entries() ]);
349
328
  } else {
350
- return url;
329
+ this.body = body;
330
+ this.url = location;
351
331
  }
352
332
  }
333
+ get location() {
334
+ return this.url;
335
+ }
353
336
  get params() {
354
- return this.entries.reduce(((params, [name, value]) => {
355
- params.append(name, value.toString());
356
- return params;
357
- }), new URLSearchParams);
337
+ return this.url.searchParams;
358
338
  }
359
339
  get entries() {
360
340
  return this.body ? Array.from(this.body.entries()) : [];
@@ -371,7 +351,7 @@ class FetchRequest {
371
351
  });
372
352
  try {
373
353
  this.delegate.requestStarted(this);
374
- const response = await fetch(this.url, fetchOptions);
354
+ const response = await fetch(this.url.href, fetchOptions);
375
355
  return await this.receive(response);
376
356
  } catch (error) {
377
357
  this.delegate.requestErrored(this, error);
@@ -403,7 +383,7 @@ class FetchRequest {
403
383
  credentials: "same-origin",
404
384
  headers: this.headers,
405
385
  redirect: "follow",
406
- body: this.isIdempotent ? undefined : this.body,
386
+ body: this.body,
407
387
  signal: this.abortSignal
408
388
  };
409
389
  }
@@ -411,20 +391,34 @@ class FetchRequest {
411
391
  return this.method == FetchMethod.get;
412
392
  }
413
393
  get headers() {
414
- return Object.assign({
415
- Accept: "text/html, application/xhtml+xml"
416
- }, this.additionalHeaders);
417
- }
418
- get additionalHeaders() {
419
- if (typeof this.delegate.additionalHeadersForRequest == "function") {
420
- return this.delegate.additionalHeadersForRequest(this);
421
- } else {
422
- return {};
394
+ const headers = Object.assign({}, this.defaultHeaders);
395
+ if (typeof this.delegate.prepareHeadersForRequest == "function") {
396
+ this.delegate.prepareHeadersForRequest(headers, this);
423
397
  }
398
+ return headers;
424
399
  }
425
400
  get abortSignal() {
426
401
  return this.abortController.signal;
427
402
  }
403
+ get defaultHeaders() {
404
+ return {
405
+ Accept: "text/html, application/xhtml+xml"
406
+ };
407
+ }
408
+ }
409
+
410
+ function mergeFormDataEntries(url, entries) {
411
+ const currentSearchParams = new URLSearchParams(url.search);
412
+ for (const [name, value] of entries) {
413
+ if (value instanceof File) continue;
414
+ if (currentSearchParams.has(name)) {
415
+ currentSearchParams.delete(name);
416
+ url.searchParams.set(name, value);
417
+ } else {
418
+ url.searchParams.append(name, value);
419
+ }
420
+ }
421
+ return url;
428
422
  }
429
423
 
430
424
  class AppearanceObserver {
@@ -454,6 +448,41 @@ class AppearanceObserver {
454
448
  }
455
449
  }
456
450
 
451
+ class StreamMessage {
452
+ constructor(html) {
453
+ this.templateElement = document.createElement("template");
454
+ this.templateElement.innerHTML = html;
455
+ }
456
+ static wrap(message) {
457
+ if (typeof message == "string") {
458
+ return new this(message);
459
+ } else {
460
+ return message;
461
+ }
462
+ }
463
+ get fragment() {
464
+ const fragment = document.createDocumentFragment();
465
+ for (const element of this.foreignElements) {
466
+ fragment.appendChild(document.importNode(element, true));
467
+ }
468
+ return fragment;
469
+ }
470
+ get foreignElements() {
471
+ return this.templateChildren.reduce(((streamElements, child) => {
472
+ if (child.tagName.toLowerCase() == "turbo-stream") {
473
+ return [ ...streamElements, child ];
474
+ } else {
475
+ return streamElements;
476
+ }
477
+ }), []);
478
+ }
479
+ get templateChildren() {
480
+ return Array.from(this.templateElement.content.children);
481
+ }
482
+ }
483
+
484
+ StreamMessage.contentType = "text/vnd.turbo-stream.html";
485
+
457
486
  var FormSubmissionState;
458
487
 
459
488
  (function(FormSubmissionState) {
@@ -465,14 +494,35 @@ var FormSubmissionState;
465
494
  FormSubmissionState[FormSubmissionState["stopped"] = 5] = "stopped";
466
495
  })(FormSubmissionState || (FormSubmissionState = {}));
467
496
 
497
+ var FormEnctype;
498
+
499
+ (function(FormEnctype) {
500
+ FormEnctype["urlEncoded"] = "application/x-www-form-urlencoded";
501
+ FormEnctype["multipart"] = "multipart/form-data";
502
+ FormEnctype["plain"] = "text/plain";
503
+ })(FormEnctype || (FormEnctype = {}));
504
+
505
+ function formEnctypeFromString(encoding) {
506
+ switch (encoding.toLowerCase()) {
507
+ case FormEnctype.multipart:
508
+ return FormEnctype.multipart;
509
+
510
+ case FormEnctype.plain:
511
+ return FormEnctype.plain;
512
+
513
+ default:
514
+ return FormEnctype.urlEncoded;
515
+ }
516
+ }
517
+
468
518
  class FormSubmission {
469
519
  constructor(delegate, formElement, submitter, mustRedirect = false) {
470
520
  this.state = FormSubmissionState.initialized;
471
521
  this.delegate = delegate;
472
522
  this.formElement = formElement;
473
- this.formData = buildFormData(formElement, submitter);
474
523
  this.submitter = submitter;
475
- this.fetchRequest = new FetchRequest(this, this.method, this.location, this.formData);
524
+ this.formData = buildFormData(formElement, submitter);
525
+ this.fetchRequest = new FetchRequest(this, this.method, this.location, this.body);
476
526
  this.mustRedirect = mustRedirect;
477
527
  }
478
528
  get method() {
@@ -485,7 +535,21 @@ class FormSubmission {
485
535
  return ((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formaction")) || this.formElement.action;
486
536
  }
487
537
  get location() {
488
- return Location.wrap(this.action);
538
+ return expandURL(this.action);
539
+ }
540
+ get body() {
541
+ if (this.enctype == FormEnctype.urlEncoded || this.method == FetchMethod.get) {
542
+ return new URLSearchParams(this.stringFormData);
543
+ } else {
544
+ return this.formData;
545
+ }
546
+ }
547
+ get enctype() {
548
+ var _a;
549
+ return formEnctypeFromString(((_a = this.submitter) === null || _a === void 0 ? void 0 : _a.getAttribute("formenctype")) || this.formElement.enctype);
550
+ }
551
+ get stringFormData() {
552
+ return [ ...this.formData ].reduce(((entries, [name, value]) => entries.concat(typeof value == "string" ? [ [ name, value ] ] : [])), []);
489
553
  }
490
554
  async start() {
491
555
  const {initialized: initialized, requesting: requesting} = FormSubmissionState;
@@ -502,15 +566,14 @@ class FormSubmission {
502
566
  return true;
503
567
  }
504
568
  }
505
- additionalHeadersForRequest(request) {
506
- const headers = {};
507
- if (this.method != FetchMethod.get) {
569
+ prepareHeadersForRequest(headers, request) {
570
+ if (!request.isIdempotent) {
508
571
  const token = getCookieValue(getMetaContent("csrf-param")) || getMetaContent("csrf-token");
509
572
  if (token) {
510
573
  headers["X-CSRF-Token"] = token;
511
574
  }
575
+ headers["Accept"] = [ StreamMessage.contentType, headers["Accept"] ].join(", ");
512
576
  }
513
- return headers;
514
577
  }
515
578
  requestStarted(request) {
516
579
  this.state = FormSubmissionState.waiting;
@@ -602,6 +665,37 @@ function responseSucceededWithoutRedirect(response) {
602
665
  return response.statusCode == 200 && !response.redirected;
603
666
  }
604
667
 
668
+ class Snapshot {
669
+ constructor(element) {
670
+ this.element = element;
671
+ }
672
+ get children() {
673
+ return [ ...this.element.children ];
674
+ }
675
+ hasAnchor(anchor) {
676
+ return this.getElementForAnchor(anchor) != null;
677
+ }
678
+ getElementForAnchor(anchor) {
679
+ try {
680
+ return this.element.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
681
+ } catch (_a) {
682
+ return null;
683
+ }
684
+ }
685
+ get firstAutofocusableElement() {
686
+ return this.element.querySelector("[autofocus]");
687
+ }
688
+ get permanentElements() {
689
+ return [ ...this.element.querySelectorAll("[id][data-turbo-permanent]") ];
690
+ }
691
+ getPermanentElementById(id) {
692
+ return this.element.querySelector(`#${id}[data-turbo-permanent]`);
693
+ }
694
+ getPermanentElementsPresentInSnapshot(snapshot) {
695
+ return this.permanentElements.filter((({id: id}) => snapshot.getPermanentElementById(id)));
696
+ }
697
+ }
698
+
605
699
  class FormInterceptor {
606
700
  constructor(delegate, element) {
607
701
  this.submitBubbled = event => {
@@ -626,6 +720,82 @@ class FormInterceptor {
626
720
  }
627
721
  }
628
722
 
723
+ class View {
724
+ constructor(delegate, element) {
725
+ this.delegate = delegate;
726
+ this.element = element;
727
+ }
728
+ scrollToAnchor(anchor) {
729
+ const element = this.snapshot.getElementForAnchor(anchor);
730
+ if (element) {
731
+ this.scrollToElement(element);
732
+ } else {
733
+ this.scrollToPosition({
734
+ x: 0,
735
+ y: 0
736
+ });
737
+ }
738
+ }
739
+ scrollToElement(element) {
740
+ element.scrollIntoView();
741
+ }
742
+ scrollToPosition({x: x, y: y}) {
743
+ this.scrollRoot.scrollTo(x, y);
744
+ }
745
+ get scrollRoot() {
746
+ return window;
747
+ }
748
+ async render(renderer) {
749
+ if (this.renderer) {
750
+ throw new Error("rendering is already in progress");
751
+ }
752
+ const {isPreview: isPreview, shouldRender: shouldRender, newSnapshot: snapshot} = renderer;
753
+ if (shouldRender) {
754
+ try {
755
+ this.renderer = renderer;
756
+ this.prepareToRenderSnapshot(renderer);
757
+ this.delegate.viewWillRenderSnapshot(snapshot, isPreview);
758
+ await this.renderSnapshot(renderer);
759
+ this.delegate.viewRenderedSnapshot(snapshot, isPreview);
760
+ this.finishRenderingSnapshot(renderer);
761
+ } finally {
762
+ delete this.renderer;
763
+ }
764
+ } else {
765
+ this.invalidate();
766
+ }
767
+ }
768
+ invalidate() {
769
+ this.delegate.viewInvalidated();
770
+ }
771
+ prepareToRenderSnapshot(renderer) {
772
+ this.markAsPreview(renderer.isPreview);
773
+ renderer.prepareToRender();
774
+ }
775
+ markAsPreview(isPreview) {
776
+ if (isPreview) {
777
+ this.element.setAttribute("data-turbo-preview", "");
778
+ } else {
779
+ this.element.removeAttribute("data-turbo-preview");
780
+ }
781
+ }
782
+ async renderSnapshot(renderer) {
783
+ await renderer.render();
784
+ }
785
+ finishRenderingSnapshot(renderer) {
786
+ renderer.finishRendering();
787
+ }
788
+ }
789
+
790
+ class FrameView extends View {
791
+ invalidate() {
792
+ this.element.innerHTML = "";
793
+ }
794
+ get snapshot() {
795
+ return new Snapshot(this.element);
796
+ }
797
+ }
798
+
629
799
  class LinkInterceptor {
630
800
  constructor(delegate, element) {
631
801
  this.clickBubbled = event => {
@@ -667,10 +837,159 @@ class LinkInterceptor {
667
837
  }
668
838
  }
669
839
 
840
+ class Renderer {
841
+ constructor(currentSnapshot, newSnapshot, isPreview) {
842
+ this.currentSnapshot = currentSnapshot;
843
+ this.newSnapshot = newSnapshot;
844
+ this.isPreview = isPreview;
845
+ this.promise = new Promise(((resolve, reject) => this.resolvingFunctions = {
846
+ resolve: resolve,
847
+ reject: reject
848
+ }));
849
+ }
850
+ get shouldRender() {
851
+ return true;
852
+ }
853
+ prepareToRender() {
854
+ return;
855
+ }
856
+ finishRendering() {
857
+ if (this.resolvingFunctions) {
858
+ this.resolvingFunctions.resolve();
859
+ delete this.resolvingFunctions;
860
+ }
861
+ }
862
+ createScriptElement(element) {
863
+ if (element.getAttribute("data-turbo-eval") == "false") {
864
+ return element;
865
+ } else {
866
+ const createdScriptElement = document.createElement("script");
867
+ createdScriptElement.textContent = element.textContent;
868
+ createdScriptElement.async = false;
869
+ copyElementAttributes(createdScriptElement, element);
870
+ return createdScriptElement;
871
+ }
872
+ }
873
+ preservingPermanentElements(callback) {
874
+ const placeholders = relocatePermanentElements(this.currentSnapshot, this.newSnapshot);
875
+ callback();
876
+ replacePlaceholderElementsWithClonedPermanentElements(placeholders);
877
+ }
878
+ focusFirstAutofocusableElement() {
879
+ const element = this.newSnapshot.firstAutofocusableElement;
880
+ if (elementIsFocusable(element)) {
881
+ element.focus();
882
+ }
883
+ }
884
+ get currentElement() {
885
+ return this.currentSnapshot.element;
886
+ }
887
+ get newElement() {
888
+ return this.newSnapshot.element;
889
+ }
890
+ }
891
+
892
+ function replaceElementWithElement(fromElement, toElement) {
893
+ const parentElement = fromElement.parentElement;
894
+ if (parentElement) {
895
+ return parentElement.replaceChild(toElement, fromElement);
896
+ }
897
+ }
898
+
899
+ function copyElementAttributes(destinationElement, sourceElement) {
900
+ for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
901
+ destinationElement.setAttribute(name, value);
902
+ }
903
+ }
904
+
905
+ function createPlaceholderForPermanentElement(permanentElement) {
906
+ const element = document.createElement("meta");
907
+ element.setAttribute("name", "turbo-permanent-placeholder");
908
+ element.setAttribute("content", permanentElement.id);
909
+ return {
910
+ element: element,
911
+ permanentElement: permanentElement
912
+ };
913
+ }
914
+
915
+ function replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
916
+ for (const {element: element, permanentElement: permanentElement} of placeholders) {
917
+ const clonedElement = permanentElement.cloneNode(true);
918
+ replaceElementWithElement(element, clonedElement);
919
+ }
920
+ }
921
+
922
+ function relocatePermanentElements(currentSnapshot, newSnapshot) {
923
+ return currentSnapshot.getPermanentElementsPresentInSnapshot(newSnapshot).reduce(((placeholders, permanentElement) => {
924
+ const newElement = newSnapshot.getPermanentElementById(permanentElement.id);
925
+ if (newElement) {
926
+ const placeholder = createPlaceholderForPermanentElement(permanentElement);
927
+ replaceElementWithElement(permanentElement, placeholder.element);
928
+ replaceElementWithElement(newElement, permanentElement);
929
+ return [ ...placeholders, placeholder ];
930
+ } else {
931
+ return placeholders;
932
+ }
933
+ }), []);
934
+ }
935
+
936
+ function elementIsFocusable(element) {
937
+ return element && typeof element.focus == "function";
938
+ }
939
+
940
+ class FrameRenderer extends Renderer {
941
+ get shouldRender() {
942
+ return true;
943
+ }
944
+ async render() {
945
+ await nextAnimationFrame();
946
+ this.preservingPermanentElements((() => {
947
+ this.loadFrameElement();
948
+ }));
949
+ this.scrollFrameIntoView();
950
+ await nextAnimationFrame();
951
+ this.focusFirstAutofocusableElement();
952
+ }
953
+ loadFrameElement() {
954
+ var _a;
955
+ const destinationRange = document.createRange();
956
+ destinationRange.selectNodeContents(this.currentElement);
957
+ destinationRange.deleteContents();
958
+ const frameElement = this.newElement;
959
+ const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
960
+ if (sourceRange) {
961
+ sourceRange.selectNodeContents(frameElement);
962
+ this.currentElement.appendChild(sourceRange.extractContents());
963
+ }
964
+ }
965
+ scrollFrameIntoView() {
966
+ if (this.currentElement.autoscroll || this.newElement.autoscroll) {
967
+ const element = this.currentElement.firstElementChild;
968
+ const block = readScrollLogicalPosition(this.currentElement.getAttribute("data-autoscroll-block"), "end");
969
+ if (element) {
970
+ element.scrollIntoView({
971
+ block: block
972
+ });
973
+ return true;
974
+ }
975
+ }
976
+ return false;
977
+ }
978
+ }
979
+
980
+ function readScrollLogicalPosition(value, defaultValue) {
981
+ if (value == "end" || value == "start" || value == "center" || value == "nearest") {
982
+ return value;
983
+ } else {
984
+ return defaultValue;
985
+ }
986
+ }
987
+
670
988
  class FrameController {
671
989
  constructor(element) {
672
990
  this.resolveVisitPromise = () => {};
673
991
  this.element = element;
992
+ this.view = new FrameView(this, this.element);
674
993
  this.appearanceObserver = new AppearanceObserver(this, this.element);
675
994
  this.linkInterceptor = new LinkInterceptor(this, this.element);
676
995
  this.formInterceptor = new FormInterceptor(this, this.element);
@@ -713,14 +1032,17 @@ class FrameController {
713
1032
  }
714
1033
  }
715
1034
  async loadResponse(response) {
716
- const fragment = fragmentFromHTML(await response.responseHTML);
717
- if (fragment) {
718
- const element = await this.extractForeignFrameElement(fragment);
719
- await nextAnimationFrame();
720
- this.loadFrameElement(element);
721
- this.scrollFrameIntoView(element);
722
- await nextAnimationFrame();
723
- this.focusFirstAutofocusableElement();
1035
+ try {
1036
+ const html = await response.responseHTML;
1037
+ if (html) {
1038
+ const {body: body} = parseHTMLDocument(html);
1039
+ const snapshot = new Snapshot(await this.extractForeignFrameElement(body));
1040
+ const renderer = new FrameRenderer(this.view.snapshot, snapshot, false);
1041
+ await this.view.render(renderer);
1042
+ }
1043
+ } catch (error) {
1044
+ console.error(error);
1045
+ this.view.invalidate();
724
1046
  }
725
1047
  }
726
1048
  elementAppearedInViewport(element) {
@@ -741,15 +1063,13 @@ class FrameController {
741
1063
  }
742
1064
  this.formSubmission = new FormSubmission(this, element, submitter);
743
1065
  if (this.formSubmission.fetchRequest.isIdempotent) {
744
- this.navigateFrame(element, this.formSubmission.fetchRequest.url);
1066
+ this.navigateFrame(element, this.formSubmission.fetchRequest.url.href);
745
1067
  } else {
746
1068
  this.formSubmission.start();
747
1069
  }
748
1070
  }
749
- additionalHeadersForRequest(request) {
750
- return {
751
- "Turbo-Frame": this.id
752
- };
1071
+ prepareHeadersForRequest(headers, request) {
1072
+ headers["Turbo-Frame"] = this.id;
753
1073
  }
754
1074
  requestStarted(request) {
755
1075
  this.element.setAttribute("busy", "");
@@ -782,9 +1102,11 @@ class FrameController {
782
1102
  }
783
1103
  formSubmissionErrored(formSubmission, error) {}
784
1104
  formSubmissionFinished(formSubmission) {}
1105
+ viewWillRenderSnapshot(snapshot, isPreview) {}
1106
+ viewRenderedSnapshot(snapshot, isPreview) {}
1107
+ viewInvalidated() {}
785
1108
  async visit(url) {
786
- const location = Location.wrap(url);
787
- const request = new FetchRequest(this, FetchMethod.get, location);
1109
+ const request = new FetchRequest(this, FetchMethod.get, expandURL(url));
788
1110
  return new Promise((resolve => {
789
1111
  this.resolveVisitPromise = () => {
790
1112
  this.resolveVisitPromise = () => {};
@@ -799,7 +1121,7 @@ class FrameController {
799
1121
  }
800
1122
  findFrameElement(element) {
801
1123
  var _a;
802
- const id = element.getAttribute("data-turbo-frame");
1124
+ const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
803
1125
  return (_a = getFrameElementById(id)) !== null && _a !== void 0 ? _a : this.element;
804
1126
  }
805
1127
  async extractForeignFrameElement(container) {
@@ -815,38 +1137,6 @@ class FrameController {
815
1137
  console.error(`Response has no matching <turbo-frame id="${id}"> element`);
816
1138
  return new FrameElement;
817
1139
  }
818
- loadFrameElement(frameElement) {
819
- var _a;
820
- const destinationRange = document.createRange();
821
- destinationRange.selectNodeContents(this.element);
822
- destinationRange.deleteContents();
823
- const sourceRange = (_a = frameElement.ownerDocument) === null || _a === void 0 ? void 0 : _a.createRange();
824
- if (sourceRange) {
825
- sourceRange.selectNodeContents(frameElement);
826
- this.element.appendChild(sourceRange.extractContents());
827
- }
828
- }
829
- focusFirstAutofocusableElement() {
830
- const element = this.firstAutofocusableElement;
831
- if (element) {
832
- element.focus();
833
- return true;
834
- }
835
- return false;
836
- }
837
- scrollFrameIntoView(frame) {
838
- if (this.element.autoscroll || frame.autoscroll) {
839
- const element = this.element.firstElementChild;
840
- const block = readScrollLogicalPosition(this.element.getAttribute("data-autoscroll-block"), "end");
841
- if (element) {
842
- element.scrollIntoView({
843
- block: block
844
- });
845
- return true;
846
- }
847
- }
848
- return false;
849
- }
850
1140
  shouldInterceptNavigation(element) {
851
1141
  const id = element.getAttribute("data-turbo-frame") || this.element.getAttribute("target");
852
1142
  if (!this.enabled || id == "_top") {
@@ -860,10 +1150,6 @@ class FrameController {
860
1150
  }
861
1151
  return true;
862
1152
  }
863
- get firstAutofocusableElement() {
864
- const element = this.element.querySelector("[autofocus]");
865
- return element instanceof HTMLElement ? element : null;
866
- }
867
1153
  get id() {
868
1154
  return this.element.id;
869
1155
  }
@@ -893,21 +1179,6 @@ function getFrameElementById(id) {
893
1179
  }
894
1180
  }
895
1181
 
896
- function readScrollLogicalPosition(value, defaultValue) {
897
- if (value == "end" || value == "start" || value == "center" || value == "nearest") {
898
- return value;
899
- } else {
900
- return defaultValue;
901
- }
902
- }
903
-
904
- function fragmentFromHTML(html) {
905
- if (html) {
906
- const foreignDocument = document.implementation.createHTMLDocument();
907
- return foreignDocument.createRange().createContextualFragment(html);
908
- }
909
- }
910
-
911
1182
  function activateElement(element) {
912
1183
  if (element && element.ownerDocument !== document) {
913
1184
  element = document.importNode(element, true);
@@ -1138,9 +1409,10 @@ class ProgressBar {
1138
1409
 
1139
1410
  ProgressBar.animationDuration = 300;
1140
1411
 
1141
- class HeadDetails {
1142
- constructor(children) {
1143
- this.detailsByOuterHTML = children.reduce(((result, element) => {
1412
+ class HeadSnapshot extends Snapshot {
1413
+ constructor() {
1414
+ super(...arguments);
1415
+ this.detailsByOuterHTML = this.children.reduce(((result, element) => {
1144
1416
  const {outerHTML: outerHTML} = element;
1145
1417
  const details = outerHTML in result ? result[outerHTML] : {
1146
1418
  type: elementType(element),
@@ -1154,23 +1426,19 @@ class HeadDetails {
1154
1426
  });
1155
1427
  }), {});
1156
1428
  }
1157
- static fromHeadElement(headElement) {
1158
- const children = headElement ? [ ...headElement.children ] : [];
1159
- return new this(children);
1160
- }
1161
- getTrackedElementSignature() {
1429
+ get trackedElementSignature() {
1162
1430
  return Object.keys(this.detailsByOuterHTML).filter((outerHTML => this.detailsByOuterHTML[outerHTML].tracked)).join("");
1163
1431
  }
1164
- getScriptElementsNotInDetails(headDetails) {
1165
- return this.getElementsMatchingTypeNotInDetails("script", headDetails);
1432
+ getScriptElementsNotInSnapshot(snapshot) {
1433
+ return this.getElementsMatchingTypeNotInSnapshot("script", snapshot);
1166
1434
  }
1167
- getStylesheetElementsNotInDetails(headDetails) {
1168
- return this.getElementsMatchingTypeNotInDetails("stylesheet", headDetails);
1435
+ getStylesheetElementsNotInSnapshot(snapshot) {
1436
+ return this.getElementsMatchingTypeNotInSnapshot("stylesheet", snapshot);
1169
1437
  }
1170
- getElementsMatchingTypeNotInDetails(matchedType, headDetails) {
1171
- return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in headDetails.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
1438
+ getElementsMatchingTypeNotInSnapshot(matchedType, snapshot) {
1439
+ return Object.keys(this.detailsByOuterHTML).filter((outerHTML => !(outerHTML in snapshot.detailsByOuterHTML))).map((outerHTML => this.detailsByOuterHTML[outerHTML])).filter((({type: type}) => type == matchedType)).map((({elements: [element]}) => element));
1172
1440
  }
1173
- getProvisionalElements() {
1441
+ get provisionalElements() {
1174
1442
  return Object.keys(this.detailsByOuterHTML).reduce(((result, outerHTML) => {
1175
1443
  const {type: type, tracked: tracked, elements: elements} = this.detailsByOuterHTML[outerHTML];
1176
1444
  if (type == null && !tracked) {
@@ -1221,75 +1489,45 @@ function elementIsMetaElementWithName(element, name) {
1221
1489
  return tagName == "meta" && element.getAttribute("name") == name;
1222
1490
  }
1223
1491
 
1224
- class Snapshot {
1225
- constructor(headDetails, bodyElement) {
1226
- this.headDetails = headDetails;
1227
- this.bodyElement = bodyElement;
1228
- }
1229
- static wrap(value) {
1230
- if (value instanceof this) {
1231
- return value;
1232
- } else if (typeof value == "string") {
1233
- return this.fromHTMLString(value);
1234
- } else {
1235
- return this.fromHTMLElement(value);
1236
- }
1237
- }
1238
- static fromHTMLString(html) {
1239
- const {documentElement: documentElement} = (new DOMParser).parseFromString(html, "text/html");
1240
- return this.fromHTMLElement(documentElement);
1241
- }
1242
- static fromHTMLElement(htmlElement) {
1243
- const headElement = htmlElement.querySelector("head");
1244
- const bodyElement = htmlElement.querySelector("body") || document.createElement("body");
1245
- const headDetails = HeadDetails.fromHeadElement(headElement);
1246
- return new this(headDetails, bodyElement);
1247
- }
1248
- clone() {
1249
- const {bodyElement: bodyElement} = Snapshot.fromHTMLString(this.bodyElement.outerHTML);
1250
- return new Snapshot(this.headDetails, bodyElement);
1251
- }
1252
- getRootLocation() {
1253
- const root = this.getSetting("root", "/");
1254
- return new Location(root);
1492
+ class PageSnapshot extends Snapshot {
1493
+ constructor(element, headSnapshot) {
1494
+ super(element);
1495
+ this.headSnapshot = headSnapshot;
1255
1496
  }
1256
- getCacheControlValue() {
1257
- return this.getSetting("cache-control");
1497
+ static fromHTMLString(html = "") {
1498
+ return this.fromDocument(parseHTMLDocument(html));
1258
1499
  }
1259
- getElementForAnchor(anchor) {
1260
- try {
1261
- return this.bodyElement.querySelector(`[id='${anchor}'], a[name='${anchor}']`);
1262
- } catch (_a) {
1263
- return null;
1264
- }
1500
+ static fromElement(element) {
1501
+ return this.fromDocument(element.ownerDocument);
1265
1502
  }
1266
- getPermanentElements() {
1267
- return [ ...this.bodyElement.querySelectorAll("[id][data-turbo-permanent]") ];
1503
+ static fromDocument({head: head, body: body}) {
1504
+ return new this(body, new HeadSnapshot(head));
1268
1505
  }
1269
- getPermanentElementById(id) {
1270
- return this.bodyElement.querySelector(`#${id}[data-turbo-permanent]`);
1506
+ clone() {
1507
+ return new PageSnapshot(this.element.cloneNode(true), this.headSnapshot);
1271
1508
  }
1272
- getPermanentElementsPresentInSnapshot(snapshot) {
1273
- return this.getPermanentElements().filter((({id: id}) => snapshot.getPermanentElementById(id)));
1509
+ get headElement() {
1510
+ return this.headSnapshot.element;
1274
1511
  }
1275
- findFirstAutofocusableElement() {
1276
- return this.bodyElement.querySelector("[autofocus]");
1512
+ get rootLocation() {
1513
+ var _a;
1514
+ const root = (_a = this.getSetting("root")) !== null && _a !== void 0 ? _a : "/";
1515
+ return expandURL(root);
1277
1516
  }
1278
- hasAnchor(anchor) {
1279
- return this.getElementForAnchor(anchor) != null;
1517
+ get cacheControlValue() {
1518
+ return this.getSetting("cache-control");
1280
1519
  }
1281
- isPreviewable() {
1282
- return this.getCacheControlValue() != "no-preview";
1520
+ get isPreviewable() {
1521
+ return this.cacheControlValue != "no-preview";
1283
1522
  }
1284
- isCacheable() {
1285
- return this.getCacheControlValue() != "no-cache";
1523
+ get isCacheable() {
1524
+ return this.cacheControlValue != "no-cache";
1286
1525
  }
1287
- isVisitable() {
1526
+ get isVisitable() {
1288
1527
  return this.getSetting("visit-control") != "reload";
1289
1528
  }
1290
- getSetting(name, defaultValue) {
1291
- const value = this.headDetails.getMetaValue(`turbo-${name}`);
1292
- return value == null ? defaultValue : value;
1529
+ getSetting(name) {
1530
+ return this.headSnapshot.getMetaValue(`turbo-${name}`);
1293
1531
  }
1294
1532
  }
1295
1533
 
@@ -1334,16 +1572,6 @@ class Visit {
1334
1572
  this.scrolled = false;
1335
1573
  this.snapshotCached = false;
1336
1574
  this.state = VisitState.initialized;
1337
- this.performScroll = () => {
1338
- if (!this.scrolled) {
1339
- if (this.action == "restore") {
1340
- this.scrollToRestoredPosition() || this.scrollToTop();
1341
- } else {
1342
- this.scrollToAnchor() || this.scrollToTop();
1343
- }
1344
- this.scrolled = true;
1345
- }
1346
- };
1347
1575
  this.delegate = delegate;
1348
1576
  this.location = location;
1349
1577
  this.restorationIdentifier = restorationIdentifier || uuid();
@@ -1398,8 +1626,9 @@ class Visit {
1398
1626
  }
1399
1627
  }
1400
1628
  changeHistory() {
1629
+ var _a;
1401
1630
  if (!this.historyChanged) {
1402
- const actionForHistory = this.location.isEqualTo(this.referrer) ? "replace" : this.action;
1631
+ const actionForHistory = this.location.href === ((_a = this.referrer) === null || _a === void 0 ? void 0 : _a.href) ? "replace" : this.action;
1403
1632
  const method = this.getHistoryMethodForAction(actionForHistory);
1404
1633
  this.history.update(method, this.location, this.restorationIdentifier);
1405
1634
  this.historyChanged = true;
@@ -1442,18 +1671,14 @@ class Visit {
1442
1671
  loadResponse() {
1443
1672
  if (this.response) {
1444
1673
  const {statusCode: statusCode, responseHTML: responseHTML} = this.response;
1445
- this.render((() => {
1674
+ this.render((async () => {
1446
1675
  this.cacheSnapshot();
1447
1676
  if (isSuccessful(statusCode) && responseHTML != null) {
1448
- this.view.render({
1449
- snapshot: Snapshot.fromHTMLString(responseHTML)
1450
- }, this.performScroll);
1677
+ await this.view.renderPage(PageSnapshot.fromHTMLString(responseHTML));
1451
1678
  this.adapter.visitRendered(this);
1452
1679
  this.complete();
1453
1680
  } else {
1454
- this.view.render({
1455
- error: responseHTML
1456
- }, this.performScroll);
1681
+ await this.view.renderError(PageSnapshot.fromHTMLString(responseHTML));
1457
1682
  this.adapter.visitRendered(this);
1458
1683
  this.fail();
1459
1684
  }
@@ -1462,15 +1687,15 @@ class Visit {
1462
1687
  }
1463
1688
  getCachedSnapshot() {
1464
1689
  const snapshot = this.view.getCachedSnapshotForLocation(this.location) || this.getPreloadedSnapshot();
1465
- if (snapshot && (!this.location.anchor || snapshot.hasAnchor(this.location.anchor))) {
1466
- if (this.action == "restore" || snapshot.isPreviewable()) {
1690
+ if (snapshot && (!getAnchor(this.location) || snapshot.hasAnchor(getAnchor(this.location)))) {
1691
+ if (this.action == "restore" || snapshot.isPreviewable) {
1467
1692
  return snapshot;
1468
1693
  }
1469
1694
  }
1470
1695
  }
1471
1696
  getPreloadedSnapshot() {
1472
1697
  if (this.snapshotHTML) {
1473
- return Snapshot.wrap(this.snapshotHTML);
1698
+ return PageSnapshot.fromHTMLString(this.snapshotHTML);
1474
1699
  }
1475
1700
  }
1476
1701
  hasCachedSnapshot() {
@@ -1480,12 +1705,9 @@ class Visit {
1480
1705
  const snapshot = this.getCachedSnapshot();
1481
1706
  if (snapshot) {
1482
1707
  const isPreview = this.shouldIssueRequest();
1483
- this.render((() => {
1708
+ this.render((async () => {
1484
1709
  this.cacheSnapshot();
1485
- this.view.render({
1486
- snapshot: snapshot,
1487
- isPreview: isPreview
1488
- }, this.performScroll);
1710
+ await this.view.renderPage(snapshot);
1489
1711
  this.adapter.visitRendered(this);
1490
1712
  if (!isPreview) {
1491
1713
  this.complete();
@@ -1539,6 +1761,16 @@ class Visit {
1539
1761
  requestFinished() {
1540
1762
  this.finishRequest();
1541
1763
  }
1764
+ performScroll() {
1765
+ if (!this.scrolled) {
1766
+ if (this.action == "restore") {
1767
+ this.scrollToRestoredPosition() || this.scrollToTop();
1768
+ } else {
1769
+ this.scrollToAnchor() || this.scrollToTop();
1770
+ }
1771
+ this.scrolled = true;
1772
+ }
1773
+ }
1542
1774
  scrollToRestoredPosition() {
1543
1775
  const {scrollPosition: scrollPosition} = this.restorationData;
1544
1776
  if (scrollPosition) {
@@ -1547,8 +1779,8 @@ class Visit {
1547
1779
  }
1548
1780
  }
1549
1781
  scrollToAnchor() {
1550
- if (this.location.anchor != null) {
1551
- this.view.scrollToAnchor(this.location.anchor);
1782
+ if (getAnchor(this.location) != null) {
1783
+ this.view.scrollToAnchor(getAnchor(this.location));
1552
1784
  return true;
1553
1785
  }
1554
1786
  }
@@ -1586,12 +1818,14 @@ class Visit {
1586
1818
  this.snapshotCached = true;
1587
1819
  }
1588
1820
  }
1589
- render(callback) {
1821
+ async render(callback) {
1590
1822
  this.cancelRender();
1591
- this.frame = requestAnimationFrame((() => {
1592
- delete this.frame;
1593
- callback.call(this);
1823
+ await new Promise((resolve => {
1824
+ this.frame = requestAnimationFrame((() => resolve()));
1594
1825
  }));
1826
+ callback();
1827
+ delete this.frame;
1828
+ this.performScroll();
1595
1829
  }
1596
1830
  cancelRender() {
1597
1831
  if (this.frame) {
@@ -1766,11 +2000,10 @@ class History {
1766
2000
  if (this.shouldHandlePopState()) {
1767
2001
  const {turbo: turbo} = event.state || {};
1768
2002
  if (turbo) {
1769
- const location = Location.currentLocation;
1770
- this.location = location;
2003
+ this.location = new URL(window.location.href);
1771
2004
  const {restorationIdentifier: restorationIdentifier} = turbo;
1772
2005
  this.restorationIdentifier = restorationIdentifier;
1773
- this.delegate.historyPoppedToLocationWithRestorationIdentifier(location, restorationIdentifier);
2006
+ this.delegate.historyPoppedToLocationWithRestorationIdentifier(this.location, restorationIdentifier);
1774
2007
  }
1775
2008
  }
1776
2009
  };
@@ -1785,7 +2018,7 @@ class History {
1785
2018
  addEventListener("popstate", this.onPopState, false);
1786
2019
  addEventListener("load", this.onPageLoad, false);
1787
2020
  this.started = true;
1788
- this.replace(Location.currentLocation);
2021
+ this.replace(new URL(window.location.href));
1789
2022
  }
1790
2023
  }
1791
2024
  stop() {
@@ -1807,7 +2040,7 @@ class History {
1807
2040
  restorationIdentifier: restorationIdentifier
1808
2041
  }
1809
2042
  };
1810
- method.call(history, state, "", location.absoluteURL);
2043
+ method.call(history, state, "", location.href);
1811
2044
  this.location = location;
1812
2045
  this.restorationIdentifier = restorationIdentifier;
1813
2046
  }
@@ -1882,7 +2115,7 @@ class LinkClickObserver {
1882
2115
  }
1883
2116
  }
1884
2117
  getLocationForLink(link) {
1885
- return new Location(link.getAttribute("href") || "");
2118
+ return expandURL(link.getAttribute("href") || "");
1886
2119
  }
1887
2120
  }
1888
2121
 
@@ -1895,9 +2128,9 @@ class Navigator {
1895
2128
  this.delegate.visitProposedToLocation(location, options);
1896
2129
  }
1897
2130
  }
1898
- startVisit(location, restorationIdentifier, options = {}) {
2131
+ startVisit(locatable, restorationIdentifier, options = {}) {
1899
2132
  this.stop();
1900
- this.currentVisit = new Visit(this, Location.wrap(location), restorationIdentifier, Object.assign({
2133
+ this.currentVisit = new Visit(this, expandURL(locatable), restorationIdentifier, Object.assign({
1901
2134
  referrer: this.location
1902
2135
  }, options));
1903
2136
  this.currentVisit.start();
@@ -1905,7 +2138,11 @@ class Navigator {
1905
2138
  submitForm(form, submitter) {
1906
2139
  this.stop();
1907
2140
  this.formSubmission = new FormSubmission(this, form, submitter, true);
1908
- this.formSubmission.start();
2141
+ if (this.formSubmission.fetchRequest.isIdempotent) {
2142
+ this.proposeVisit(this.formSubmission.fetchRequest.url);
2143
+ } else {
2144
+ this.formSubmission.start();
2145
+ }
1909
2146
  }
1910
2147
  stop() {
1911
2148
  if (this.formSubmission) {
@@ -1948,10 +2185,8 @@ class Navigator {
1948
2185
  async formSubmissionFailedWithResponse(formSubmission, fetchResponse) {
1949
2186
  const responseHTML = await fetchResponse.responseHTML;
1950
2187
  if (responseHTML) {
1951
- const snapshot = Snapshot.fromHTMLString(responseHTML);
1952
- this.view.render({
1953
- snapshot: snapshot
1954
- }, (() => {}));
2188
+ const snapshot = PageSnapshot.fromHTMLString(responseHTML);
2189
+ await this.view.renderPage(snapshot);
1955
2190
  this.view.clearSnapshotCache();
1956
2191
  }
1957
2192
  }
@@ -2061,51 +2296,10 @@ class ScrollObserver {
2061
2296
  }
2062
2297
  }
2063
2298
 
2064
- class StreamMessage {
2065
- constructor(html) {
2066
- this.templateElement = document.createElement("template");
2067
- this.templateElement.innerHTML = html;
2068
- }
2069
- static wrap(message) {
2070
- if (typeof message == "string") {
2071
- return new this(message);
2072
- } else {
2073
- return message;
2074
- }
2075
- }
2076
- get fragment() {
2077
- const fragment = document.createDocumentFragment();
2078
- for (const element of this.foreignElements) {
2079
- fragment.appendChild(document.importNode(element, true));
2080
- }
2081
- return fragment;
2082
- }
2083
- get foreignElements() {
2084
- return this.templateChildren.reduce(((streamElements, child) => {
2085
- if (child.tagName.toLowerCase() == "turbo-stream") {
2086
- return [ ...streamElements, child ];
2087
- } else {
2088
- return streamElements;
2089
- }
2090
- }), []);
2091
- }
2092
- get templateChildren() {
2093
- return Array.from(this.templateElement.content.children);
2094
- }
2095
- }
2096
-
2097
2299
  class StreamObserver {
2098
2300
  constructor(delegate) {
2099
2301
  this.sources = new Set;
2100
2302
  this.started = false;
2101
- this.prepareFetchRequest = event => {
2102
- var _a;
2103
- const fetchOptions = (_a = event.detail) === null || _a === void 0 ? void 0 : _a.fetchOptions;
2104
- if (fetchOptions) {
2105
- const {headers: headers} = fetchOptions;
2106
- headers.Accept = [ "text/vnd.turbo-stream.html", headers.Accept ].join(", ");
2107
- }
2108
- };
2109
2303
  this.inspectFetchResponse = event => {
2110
2304
  const response = fetchResponseFromEvent(event);
2111
2305
  if (response && fetchResponseIsStream(response)) {
@@ -2123,14 +2317,12 @@ class StreamObserver {
2123
2317
  start() {
2124
2318
  if (!this.started) {
2125
2319
  this.started = true;
2126
- addEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
2127
2320
  addEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2128
2321
  }
2129
2322
  }
2130
2323
  stop() {
2131
2324
  if (this.started) {
2132
2325
  this.started = false;
2133
- removeEventListener("turbo:before-fetch-request", this.prepareFetchRequest, true);
2134
2326
  removeEventListener("turbo:before-fetch-response", this.inspectFetchResponse, false);
2135
2327
  }
2136
2328
  }
@@ -2171,70 +2363,25 @@ function fetchResponseFromEvent(event) {
2171
2363
  function fetchResponseIsStream(response) {
2172
2364
  var _a;
2173
2365
  const contentType = (_a = response.contentType) !== null && _a !== void 0 ? _a : "";
2174
- return /^text\/vnd\.turbo-stream\.html\b/.test(contentType);
2366
+ return contentType.startsWith(StreamMessage.contentType);
2175
2367
  }
2176
2368
 
2177
2369
  function isAction(action) {
2178
2370
  return action == "advance" || action == "replace" || action == "restore";
2179
2371
  }
2180
2372
 
2181
- class Renderer {
2182
- renderView(callback) {
2183
- this.delegate.viewWillRender(this.newBody);
2184
- callback();
2185
- this.delegate.viewRendered(this.newBody);
2186
- }
2187
- invalidateView() {
2188
- this.delegate.viewInvalidated();
2189
- }
2190
- createScriptElement(element) {
2191
- if (element.getAttribute("data-turbo-eval") == "false") {
2192
- return element;
2193
- } else {
2194
- const createdScriptElement = document.createElement("script");
2195
- createdScriptElement.textContent = element.textContent;
2196
- createdScriptElement.async = false;
2197
- copyElementAttributes(createdScriptElement, element);
2198
- return createdScriptElement;
2199
- }
2200
- }
2201
- }
2202
-
2203
- function copyElementAttributes(destinationElement, sourceElement) {
2204
- for (const {name: name, value: value} of [ ...sourceElement.attributes ]) {
2205
- destinationElement.setAttribute(name, value);
2206
- }
2207
- }
2208
-
2209
2373
  class ErrorRenderer extends Renderer {
2210
- constructor(delegate, html) {
2211
- super();
2212
- this.delegate = delegate;
2213
- this.htmlElement = (() => {
2214
- const htmlElement = document.createElement("html");
2215
- htmlElement.innerHTML = html;
2216
- return htmlElement;
2217
- })();
2218
- this.newHead = this.htmlElement.querySelector("head") || document.createElement("head");
2219
- this.newBody = this.htmlElement.querySelector("body") || document.createElement("body");
2220
- }
2221
- static render(delegate, callback, html) {
2222
- return new this(delegate, html).render(callback);
2223
- }
2224
- render(callback) {
2225
- this.renderView((() => {
2226
- this.replaceHeadAndBody();
2227
- this.activateBodyScriptElements();
2228
- callback();
2229
- }));
2374
+ async render() {
2375
+ this.replaceHeadAndBody();
2376
+ this.activateScriptElements();
2230
2377
  }
2231
2378
  replaceHeadAndBody() {
2232
2379
  const {documentElement: documentElement, head: head, body: body} = document;
2233
2380
  documentElement.replaceChild(this.newHead, head);
2234
- documentElement.replaceChild(this.newBody, body);
2381
+ documentElement.replaceChild(this.newElement, body);
2235
2382
  }
2236
- activateBodyScriptElements() {
2237
- for (const replaceableElement of this.getScriptElements()) {
2383
+ activateScriptElements() {
2384
+ for (const replaceableElement of this.scriptElements) {
2238
2385
  const parentNode = replaceableElement.parentNode;
2239
2386
  if (parentNode) {
2240
2387
  const element = this.createScriptElement(replaceableElement);
@@ -2242,82 +2389,38 @@ class ErrorRenderer extends Renderer {
2242
2389
  }
2243
2390
  }
2244
2391
  }
2245
- getScriptElements() {
2392
+ get newHead() {
2393
+ return this.newSnapshot.headSnapshot.element;
2394
+ }
2395
+ get scriptElements() {
2246
2396
  return [ ...document.documentElement.querySelectorAll("script") ];
2247
2397
  }
2248
2398
  }
2249
2399
 
2250
- class SnapshotCache {
2251
- constructor(size) {
2252
- this.keys = [];
2253
- this.snapshots = {};
2254
- this.size = size;
2255
- }
2256
- has(location) {
2257
- return location.toCacheKey() in this.snapshots;
2258
- }
2259
- get(location) {
2260
- if (this.has(location)) {
2261
- const snapshot = this.read(location);
2262
- this.touch(location);
2263
- return snapshot;
2264
- }
2265
- }
2266
- put(location, snapshot) {
2267
- this.write(location, snapshot);
2268
- this.touch(location);
2269
- return snapshot;
2270
- }
2271
- clear() {
2272
- this.snapshots = {};
2273
- }
2274
- read(location) {
2275
- return this.snapshots[location.toCacheKey()];
2400
+ class PageRenderer extends Renderer {
2401
+ get shouldRender() {
2402
+ return this.newSnapshot.isVisitable && this.trackedElementsAreIdentical;
2276
2403
  }
2277
- write(location, snapshot) {
2278
- this.snapshots[location.toCacheKey()] = snapshot;
2404
+ prepareToRender() {
2405
+ this.mergeHead();
2279
2406
  }
2280
- touch(location) {
2281
- const key = location.toCacheKey();
2282
- const index = this.keys.indexOf(key);
2283
- if (index > -1) this.keys.splice(index, 1);
2284
- this.keys.unshift(key);
2285
- this.trim();
2407
+ async render() {
2408
+ this.replaceBody();
2286
2409
  }
2287
- trim() {
2288
- for (const key of this.keys.splice(this.size)) {
2289
- delete this.snapshots[key];
2410
+ finishRendering() {
2411
+ super.finishRendering();
2412
+ if (this.isPreview) {
2413
+ this.focusFirstAutofocusableElement();
2290
2414
  }
2291
2415
  }
2292
- }
2293
-
2294
- class SnapshotRenderer extends Renderer {
2295
- constructor(delegate, currentSnapshot, newSnapshot, isPreview) {
2296
- super();
2297
- this.delegate = delegate;
2298
- this.currentSnapshot = currentSnapshot;
2299
- this.currentHeadDetails = currentSnapshot.headDetails;
2300
- this.newSnapshot = newSnapshot;
2301
- this.newHeadDetails = newSnapshot.headDetails;
2302
- this.newBody = newSnapshot.bodyElement;
2303
- this.isPreview = isPreview;
2416
+ get currentHeadSnapshot() {
2417
+ return this.currentSnapshot.headSnapshot;
2304
2418
  }
2305
- static render(delegate, callback, currentSnapshot, newSnapshot, isPreview) {
2306
- return new this(delegate, currentSnapshot, newSnapshot, isPreview).render(callback);
2419
+ get newHeadSnapshot() {
2420
+ return this.newSnapshot.headSnapshot;
2307
2421
  }
2308
- render(callback) {
2309
- if (this.shouldRender()) {
2310
- this.mergeHead();
2311
- this.renderView((() => {
2312
- this.replaceBody();
2313
- if (!this.isPreview) {
2314
- this.focusFirstAutofocusableElement();
2315
- }
2316
- callback();
2317
- }));
2318
- } else {
2319
- this.invalidateView();
2320
- }
2422
+ get newElement() {
2423
+ return this.newSnapshot.element;
2321
2424
  }
2322
2425
  mergeHead() {
2323
2426
  this.copyNewHeadStylesheetElements();
@@ -2326,190 +2429,145 @@ class SnapshotRenderer extends Renderer {
2326
2429
  this.copyNewHeadProvisionalElements();
2327
2430
  }
2328
2431
  replaceBody() {
2329
- const placeholders = this.relocateCurrentBodyPermanentElements();
2330
- this.activateNewBody();
2331
- this.assignNewBody();
2332
- this.replacePlaceholderElementsWithClonedPermanentElements(placeholders);
2333
- }
2334
- shouldRender() {
2335
- return this.newSnapshot.isVisitable() && this.trackedElementsAreIdentical();
2432
+ this.preservingPermanentElements((() => {
2433
+ this.activateNewBody();
2434
+ this.assignNewBody();
2435
+ }));
2336
2436
  }
2337
- trackedElementsAreIdentical() {
2338
- return this.currentHeadDetails.getTrackedElementSignature() == this.newHeadDetails.getTrackedElementSignature();
2437
+ get trackedElementsAreIdentical() {
2438
+ return this.currentHeadSnapshot.trackedElementSignature == this.newHeadSnapshot.trackedElementSignature;
2339
2439
  }
2340
2440
  copyNewHeadStylesheetElements() {
2341
- for (const element of this.getNewHeadStylesheetElements()) {
2441
+ for (const element of this.newHeadStylesheetElements) {
2342
2442
  document.head.appendChild(element);
2343
2443
  }
2344
2444
  }
2345
2445
  copyNewHeadScriptElements() {
2346
- for (const element of this.getNewHeadScriptElements()) {
2446
+ for (const element of this.newHeadScriptElements) {
2347
2447
  document.head.appendChild(this.createScriptElement(element));
2348
2448
  }
2349
2449
  }
2350
2450
  removeCurrentHeadProvisionalElements() {
2351
- for (const element of this.getCurrentHeadProvisionalElements()) {
2451
+ for (const element of this.currentHeadProvisionalElements) {
2352
2452
  document.head.removeChild(element);
2353
2453
  }
2354
2454
  }
2355
2455
  copyNewHeadProvisionalElements() {
2356
- for (const element of this.getNewHeadProvisionalElements()) {
2456
+ for (const element of this.newHeadProvisionalElements) {
2357
2457
  document.head.appendChild(element);
2358
2458
  }
2359
2459
  }
2360
- relocateCurrentBodyPermanentElements() {
2361
- return this.getCurrentBodyPermanentElements().reduce(((placeholders, permanentElement) => {
2362
- const newElement = this.newSnapshot.getPermanentElementById(permanentElement.id);
2363
- if (newElement) {
2364
- const placeholder = createPlaceholderForPermanentElement(permanentElement);
2365
- replaceElementWithElement(permanentElement, placeholder.element);
2366
- replaceElementWithElement(newElement, permanentElement);
2367
- return [ ...placeholders, placeholder ];
2368
- } else {
2369
- return placeholders;
2370
- }
2371
- }), []);
2372
- }
2373
- replacePlaceholderElementsWithClonedPermanentElements(placeholders) {
2374
- for (const {element: element, permanentElement: permanentElement} of placeholders) {
2375
- const clonedElement = permanentElement.cloneNode(true);
2376
- replaceElementWithElement(element, clonedElement);
2377
- }
2378
- }
2379
2460
  activateNewBody() {
2380
- document.adoptNode(this.newBody);
2461
+ document.adoptNode(this.newElement);
2381
2462
  this.activateNewBodyScriptElements();
2382
2463
  }
2383
2464
  activateNewBodyScriptElements() {
2384
- for (const inertScriptElement of this.getNewBodyScriptElements()) {
2465
+ for (const inertScriptElement of this.newBodyScriptElements) {
2385
2466
  const activatedScriptElement = this.createScriptElement(inertScriptElement);
2386
2467
  replaceElementWithElement(inertScriptElement, activatedScriptElement);
2387
2468
  }
2388
2469
  }
2389
2470
  assignNewBody() {
2390
- if (document.body) {
2391
- replaceElementWithElement(document.body, this.newBody);
2471
+ if (document.body && this.newElement instanceof HTMLBodyElement) {
2472
+ replaceElementWithElement(document.body, this.newElement);
2392
2473
  } else {
2393
- document.documentElement.appendChild(this.newBody);
2394
- }
2395
- }
2396
- focusFirstAutofocusableElement() {
2397
- const element = this.newSnapshot.findFirstAutofocusableElement();
2398
- if (elementIsFocusable(element)) {
2399
- element.focus();
2474
+ document.documentElement.appendChild(this.newElement);
2400
2475
  }
2401
2476
  }
2402
- getNewHeadStylesheetElements() {
2403
- return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails);
2477
+ get newHeadStylesheetElements() {
2478
+ return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot);
2404
2479
  }
2405
- getNewHeadScriptElements() {
2406
- return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails);
2480
+ get newHeadScriptElements() {
2481
+ return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot);
2407
2482
  }
2408
- getCurrentHeadProvisionalElements() {
2409
- return this.currentHeadDetails.getProvisionalElements();
2483
+ get currentHeadProvisionalElements() {
2484
+ return this.currentHeadSnapshot.provisionalElements;
2410
2485
  }
2411
- getNewHeadProvisionalElements() {
2412
- return this.newHeadDetails.getProvisionalElements();
2486
+ get newHeadProvisionalElements() {
2487
+ return this.newHeadSnapshot.provisionalElements;
2413
2488
  }
2414
- getCurrentBodyPermanentElements() {
2415
- return this.currentSnapshot.getPermanentElementsPresentInSnapshot(this.newSnapshot);
2489
+ get newBodyScriptElements() {
2490
+ return [ ...this.newElement.querySelectorAll("script") ];
2416
2491
  }
2417
- getNewBodyScriptElements() {
2418
- return [ ...this.newBody.querySelectorAll("script") ];
2419
- }
2420
- }
2421
-
2422
- function createPlaceholderForPermanentElement(permanentElement) {
2423
- const element = document.createElement("meta");
2424
- element.setAttribute("name", "turbo-permanent-placeholder");
2425
- element.setAttribute("content", permanentElement.id);
2426
- return {
2427
- element: element,
2428
- permanentElement: permanentElement
2429
- };
2430
2492
  }
2431
2493
 
2432
- function replaceElementWithElement(fromElement, toElement) {
2433
- const parentElement = fromElement.parentElement;
2434
- if (parentElement) {
2435
- return parentElement.replaceChild(toElement, fromElement);
2494
+ class SnapshotCache {
2495
+ constructor(size) {
2496
+ this.keys = [];
2497
+ this.snapshots = {};
2498
+ this.size = size;
2499
+ }
2500
+ has(location) {
2501
+ return toCacheKey(location) in this.snapshots;
2502
+ }
2503
+ get(location) {
2504
+ if (this.has(location)) {
2505
+ const snapshot = this.read(location);
2506
+ this.touch(location);
2507
+ return snapshot;
2508
+ }
2509
+ }
2510
+ put(location, snapshot) {
2511
+ this.write(location, snapshot);
2512
+ this.touch(location);
2513
+ return snapshot;
2514
+ }
2515
+ clear() {
2516
+ this.snapshots = {};
2517
+ }
2518
+ read(location) {
2519
+ return this.snapshots[toCacheKey(location)];
2520
+ }
2521
+ write(location, snapshot) {
2522
+ this.snapshots[toCacheKey(location)] = snapshot;
2523
+ }
2524
+ touch(location) {
2525
+ const key = toCacheKey(location);
2526
+ const index = this.keys.indexOf(key);
2527
+ if (index > -1) this.keys.splice(index, 1);
2528
+ this.keys.unshift(key);
2529
+ this.trim();
2530
+ }
2531
+ trim() {
2532
+ for (const key of this.keys.splice(this.size)) {
2533
+ delete this.snapshots[key];
2534
+ }
2436
2535
  }
2437
2536
  }
2438
2537
 
2439
- function elementIsFocusable(element) {
2440
- return element && typeof element.focus == "function";
2441
- }
2442
-
2443
- class View {
2444
- constructor(delegate) {
2445
- this.htmlElement = document.documentElement;
2538
+ class PageView extends View {
2539
+ constructor() {
2540
+ super(...arguments);
2446
2541
  this.snapshotCache = new SnapshotCache(10);
2447
- this.delegate = delegate;
2542
+ this.lastRenderedLocation = new URL(location.href);
2448
2543
  }
2449
- getRootLocation() {
2450
- return this.getSnapshot().getRootLocation();
2451
- }
2452
- getElementForAnchor(anchor) {
2453
- return this.getSnapshot().getElementForAnchor(anchor);
2544
+ renderPage(snapshot, isPreview = false) {
2545
+ const renderer = new PageRenderer(this.snapshot, snapshot, isPreview);
2546
+ return this.render(renderer);
2454
2547
  }
2455
- getSnapshot() {
2456
- return Snapshot.fromHTMLElement(this.htmlElement);
2548
+ renderError(snapshot) {
2549
+ const renderer = new ErrorRenderer(this.snapshot, snapshot, false);
2550
+ this.render(renderer);
2457
2551
  }
2458
2552
  clearSnapshotCache() {
2459
2553
  this.snapshotCache.clear();
2460
2554
  }
2461
- shouldCacheSnapshot() {
2462
- return this.getSnapshot().isCacheable();
2463
- }
2464
2555
  async cacheSnapshot() {
2465
- if (this.shouldCacheSnapshot()) {
2556
+ if (this.shouldCacheSnapshot) {
2466
2557
  this.delegate.viewWillCacheSnapshot();
2467
- const snapshot = this.getSnapshot();
2468
- const location = this.lastRenderedLocation || Location.currentLocation;
2469
- await nextMicrotask();
2558
+ const {snapshot: snapshot, lastRenderedLocation: location} = this;
2559
+ await nextEventLoopTick();
2470
2560
  this.snapshotCache.put(location, snapshot.clone());
2471
2561
  }
2472
2562
  }
2473
2563
  getCachedSnapshotForLocation(location) {
2474
2564
  return this.snapshotCache.get(location);
2475
2565
  }
2476
- render({snapshot: snapshot, error: error, isPreview: isPreview}, callback) {
2477
- this.markAsPreview(isPreview);
2478
- if (snapshot) {
2479
- this.renderSnapshot(snapshot, isPreview, callback);
2480
- } else {
2481
- this.renderError(error, callback);
2482
- }
2483
- }
2484
- scrollToAnchor(anchor) {
2485
- const element = this.getElementForAnchor(anchor);
2486
- if (element) {
2487
- this.scrollToElement(element);
2488
- } else {
2489
- this.scrollToPosition({
2490
- x: 0,
2491
- y: 0
2492
- });
2493
- }
2494
- }
2495
- scrollToElement(element) {
2496
- element.scrollIntoView();
2497
- }
2498
- scrollToPosition({x: x, y: y}) {
2499
- window.scrollTo(x, y);
2500
- }
2501
- markAsPreview(isPreview) {
2502
- if (isPreview) {
2503
- this.htmlElement.setAttribute("data-turbo-preview", "");
2504
- } else {
2505
- this.htmlElement.removeAttribute("data-turbo-preview");
2506
- }
2507
- }
2508
- renderSnapshot(snapshot, isPreview, callback) {
2509
- SnapshotRenderer.render(this.delegate, callback, this.getSnapshot(), snapshot, isPreview || false);
2566
+ get snapshot() {
2567
+ return PageSnapshot.fromElement(this.element);
2510
2568
  }
2511
- renderError(error, callback) {
2512
- ErrorRenderer.render(this.delegate, callback, error || "");
2569
+ get shouldCacheSnapshot() {
2570
+ return this.snapshot.isCacheable;
2513
2571
  }
2514
2572
  }
2515
2573
 
@@ -2517,7 +2575,7 @@ class Session {
2517
2575
  constructor() {
2518
2576
  this.navigator = new Navigator(this);
2519
2577
  this.history = new History(this);
2520
- this.view = new View(this);
2578
+ this.view = new PageView(this, document.documentElement);
2521
2579
  this.adapter = new BrowserAdapter(this);
2522
2580
  this.pageObserver = new PageObserver(this);
2523
2581
  this.linkClickObserver = new LinkClickObserver(this);
@@ -2561,7 +2619,7 @@ class Session {
2561
2619
  this.adapter = adapter;
2562
2620
  }
2563
2621
  visit(location, options = {}) {
2564
- this.navigator.proposeVisit(Location.wrap(location), options);
2622
+ this.navigator.proposeVisit(expandURL(location), options);
2565
2623
  }
2566
2624
  connectStreamSource(source) {
2567
2625
  this.streamObserver.connectStreamSource(source);
@@ -2604,7 +2662,7 @@ class Session {
2604
2662
  }
2605
2663
  followedLinkToLocation(link, location) {
2606
2664
  const action = this.getActionForLink(link);
2607
- this.visit(location, {
2665
+ this.visit(location.href, {
2608
2666
  action: action
2609
2667
  });
2610
2668
  }
@@ -2612,9 +2670,11 @@ class Session {
2612
2670
  return this.applicationAllowsVisitingLocation(location);
2613
2671
  }
2614
2672
  visitProposedToLocation(location, options) {
2673
+ extendURLWithDeprecatedProperties(location);
2615
2674
  this.adapter.visitProposedToLocation(location, options);
2616
2675
  }
2617
2676
  visitStarted(visit) {
2677
+ extendURLWithDeprecatedProperties(visit.location);
2618
2678
  this.notifyApplicationAfterVisitingLocation(visit.location);
2619
2679
  }
2620
2680
  visitCompleted(visit) {
@@ -2639,19 +2699,19 @@ class Session {
2639
2699
  receivedMessageFromStream(message) {
2640
2700
  this.renderStreamMessage(message);
2641
2701
  }
2642
- viewWillRender(newBody) {
2643
- this.notifyApplicationBeforeRender(newBody);
2702
+ viewWillCacheSnapshot() {
2703
+ this.notifyApplicationBeforeCachingSnapshot();
2704
+ }
2705
+ viewWillRenderSnapshot({element: element}, isPreview) {
2706
+ this.notifyApplicationBeforeRender(element);
2644
2707
  }
2645
- viewRendered() {
2708
+ viewRenderedSnapshot(snapshot, isPreview) {
2646
2709
  this.view.lastRenderedLocation = this.history.location;
2647
2710
  this.notifyApplicationAfterRender();
2648
2711
  }
2649
2712
  viewInvalidated() {
2650
2713
  this.adapter.pageInvalidated();
2651
2714
  }
2652
- viewWillCacheSnapshot() {
2653
- this.notifyApplicationBeforeCachingSnapshot();
2654
- }
2655
2715
  applicationAllowsFollowingLinkToLocation(link, location) {
2656
2716
  const event = this.notifyApplicationAfterClickingLinkToLocation(link, location);
2657
2717
  return !event.defaultPrevented;
@@ -2664,7 +2724,7 @@ class Session {
2664
2724
  return dispatch("turbo:click", {
2665
2725
  target: link,
2666
2726
  detail: {
2667
- url: location.absoluteURL
2727
+ url: location.href
2668
2728
  },
2669
2729
  cancelable: true
2670
2730
  });
@@ -2672,7 +2732,7 @@ class Session {
2672
2732
  notifyApplicationBeforeVisitingLocation(location) {
2673
2733
  return dispatch("turbo:before-visit", {
2674
2734
  detail: {
2675
- url: location.absoluteURL
2735
+ url: location.href
2676
2736
  },
2677
2737
  cancelable: true
2678
2738
  });
@@ -2680,7 +2740,7 @@ class Session {
2680
2740
  notifyApplicationAfterVisitingLocation(location) {
2681
2741
  return dispatch("turbo:visit", {
2682
2742
  detail: {
2683
- url: location.absoluteURL
2743
+ url: location.href
2684
2744
  }
2685
2745
  });
2686
2746
  }
@@ -2700,7 +2760,7 @@ class Session {
2700
2760
  notifyApplicationAfterPageLoad(timing = {}) {
2701
2761
  return dispatch("turbo:load", {
2702
2762
  detail: {
2703
- url: this.location.absoluteURL,
2763
+ url: this.location.href,
2704
2764
  timing: timing
2705
2765
  }
2706
2766
  });
@@ -2718,10 +2778,25 @@ class Session {
2718
2778
  }
2719
2779
  }
2720
2780
  locationIsVisitable(location) {
2721
- return location.isPrefixedBy(this.view.getRootLocation()) && location.isHTML();
2781
+ return isPrefixedBy(location, this.snapshot.rootLocation) && isHTML(location);
2722
2782
  }
2783
+ get snapshot() {
2784
+ return this.view.snapshot;
2785
+ }
2786
+ }
2787
+
2788
+ function extendURLWithDeprecatedProperties(url) {
2789
+ Object.defineProperties(url, deprecatedLocationPropertyDescriptors);
2723
2790
  }
2724
2791
 
2792
+ const deprecatedLocationPropertyDescriptors = {
2793
+ absoluteURL: {
2794
+ get() {
2795
+ return this.toString();
2796
+ }
2797
+ }
2798
+ };
2799
+
2725
2800
  const session = new Session;
2726
2801
 
2727
2802
  const {navigator: navigator} = session;