diff --git a/src/core/drive/preloader.js b/src/core/drive/preloader.js index d23c612a6..606a1e8d1 100644 --- a/src/core/drive/preloader.js +++ b/src/core/drive/preloader.js @@ -1,31 +1,25 @@ import { PageSnapshot } from "./page_snapshot" import { FetchMethod, FetchRequest } from "../../http/fetch_request" +import { AttributeObserver } from "../../observers/attribute_observer" export class Preloader { - selector = "a[data-turbo-preload]" - - constructor(delegate, snapshotCache) { + constructor(delegate, element, snapshotCache) { this.delegate = delegate this.snapshotCache = snapshotCache + this.attributeObserver = new AttributeObserver(this, element, "data-turbo-preload") } start() { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", this.#preloadAll) - } else { - this.preloadOnLoadLinksForView(document.body) - } + this.attributeObserver.start() } stop() { - document.removeEventListener("DOMContentLoaded", this.#preloadAll) + this.attributeObserver.stop() } - preloadOnLoadLinksForView(element) { - for (const link of element.querySelectorAll(this.selector)) { - if (this.delegate.shouldPreloadLink(link)) { - this.preloadURL(link) - } + observedElementWithAttribute(element) { + if (element instanceof HTMLAnchorElement && this.delegate.shouldPreloadLink(element)) { + this.preloadURL(element) } } @@ -66,8 +60,4 @@ export class Preloader { requestPreventedHandlingResponse(fetchRequest, fetchResponse) {} requestFailedWithResponse(fetchRequest, fetchResponse) {} - - #preloadAll = () => { - this.preloadOnLoadLinksForView(document.body) - } } diff --git a/src/core/frames/frame_controller.js b/src/core/frames/frame_controller.js index e82e097f4..76a6f83d9 100644 --- a/src/core/frames/frame_controller.js +++ b/src/core/frames/frame_controller.js @@ -278,10 +278,6 @@ export class FrameController { viewRenderedSnapshot(_snapshot, _isPreview, _renderMethod) {} - preloadOnLoadLinksForView(element) { - session.preloadOnLoadLinksForView(element) - } - viewInvalidated() {} // Frame renderer delegate diff --git a/src/core/session.js b/src/core/session.js index cdb978348..2b82d99b8 100644 --- a/src/core/session.js +++ b/src/core/session.js @@ -46,7 +46,7 @@ export class Session { constructor(recentRequests) { this.recentRequests = recentRequests - this.preloader = new Preloader(this, this.view.snapshotCache) + this.preloader = new Preloader(this, document.documentElement, this.view.snapshotCache) this.debouncedRefresh = this.refresh this.pageRefreshDebouncePeriod = this.pageRefreshDebouncePeriod } @@ -331,10 +331,6 @@ export class Session { this.notifyApplicationAfterRender(renderMethod) } - preloadOnLoadLinksForView(element) { - this.preloader.preloadOnLoadLinksForView(element) - } - viewInvalidated(reason) { this.adapter.pageInvalidated(reason) } diff --git a/src/core/view.js b/src/core/view.js index 7b562563d..44ec8a4c2 100644 --- a/src/core/view.js +++ b/src/core/view.js @@ -75,7 +75,6 @@ export class View { await this.renderSnapshot(renderer) this.delegate.viewRenderedSnapshot(snapshot, isPreview, this.renderer.renderMethod) - this.delegate.preloadOnLoadLinksForView(this.element) this.finishRenderingSnapshot(renderer) } finally { delete this.renderer diff --git a/src/observers/attribute_observer.js b/src/observers/attribute_observer.js new file mode 100644 index 000000000..862de94bd --- /dev/null +++ b/src/observers/attribute_observer.js @@ -0,0 +1,61 @@ +export class AttributeObserver { + started = false + + constructor(delegate, element, attributeName) { + this.delegate = delegate + this.element = element + this.attributeName = attributeName + this.observer = new MutationObserver(this.#handleMutations) + } + + start() { + if (!this.started) { + this.observer.observe(this.element, { + attributeFilter: [this.attributeName], + subtree: true, + childList: true + }) + + this.started = true + } + + for (const element of this.#queryAllMatches(this.element)) { + this.#handleNode(element) + } + } + + stop() { + if (this.started) { + this.observer.disconnect() + this.started = false + } + } + + #handleMutations = (mutationRecords) => { + for (const { addedNodes, target, type } of mutationRecords) { + if (type === "attributes") { + this.#handleNode(target) + } else { + for (const node of addedNodes) { + if (node instanceof Element) { + this.#handleNode(node) + + for (const element of this.#queryAllMatches(node)) { + this.#handleNode(element) + } + } + } + } + } + } + + #handleNode(node) { + if (node instanceof Element && node.hasAttribute(this.attributeName)) { + this.delegate.observedElementWithAttribute(node, this.attributeName, node.getAttribute(this.attributeName)) + } + } + + #queryAllMatches(parent) { + return parent.querySelectorAll(`[${this.attributeName}]`) + } +}