turbo-rails 0.5.8 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
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;