From 959558e73ce2931d080cae685a175afbcfe1f358 Mon Sep 17 00:00:00 2001 From: Demetrio Girardi Date: Wed, 15 Mar 2023 08:01:09 -0700 Subject: [PATCH] Core: cache `rerefererInfo` as long as location does not change --- src/refererDetection.js | 60 +++++++++++++++++++----------- test/spec/refererDetection_spec.js | 57 +++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/refererDetection.js b/src/refererDetection.js index e0cb15522cc..93ebf085dd5 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -51,6 +51,26 @@ export function parseDomain(url, {noLeadingWww = false, noPort = false} = {}) { return url; } +/** + * This function returns canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage + * + * @param {Object} doc document + * @returns {string|null} + */ +function getCanonicalUrl(doc) { + try { + const element = doc.querySelector("link[rel='canonical']"); + + if (element !== null) { + return element.href; + } + } catch (e) { + // Ignore error + } + + return null; +} + /** * @param {Window} win Window * @returns {Function} @@ -75,26 +95,6 @@ export function detectReferer(win) { } } - /** - * This function returns canonical URL which refers to an HTML link element, with the attribute of rel="canonical", found in the element of your webpage - * - * @param {Object} doc document - * @returns {string|null} - */ - function getCanonicalUrl(doc) { - try { - const element = doc.querySelector("link[rel='canonical']"); - - if (element !== null) { - return element.href; - } - } catch (e) { - // Ignore error - } - - return null; - } - // TODO: the meaning of "reachedTop" seems to be intentionally ambiguous - best to leave them out of // the typedef for now. (for example, unit tests enforce that "reachedTop" should be false in some situations where we // happily provide a location for the top). @@ -260,7 +260,25 @@ export function detectReferer(win) { return refererInfo; } +// cache result of fn (= referer info) as long as: +// - we are the top window +// - canonical URL tag and window location have not changed +export function cacheWithLocation(fn, win = window) { + if (win.top !== win) return fn; + let canonical, href, value; + return function () { + const newCanonical = getCanonicalUrl(win.document); + const newHref = win.location.href; + if (canonical !== newCanonical || newHref !== href) { + canonical = newCanonical; + href = newHref; + value = fn(); + } + return value; + } +} + /** * @type {function(): refererInfo} */ -export const getRefererInfo = detectReferer(window); +export const getRefererInfo = cacheWithLocation(detectReferer(window)); diff --git a/test/spec/refererDetection_spec.js b/test/spec/refererDetection_spec.js index a0ffc3eddbe..800222892e6 100644 --- a/test/spec/refererDetection_spec.js +++ b/test/spec/refererDetection_spec.js @@ -1,4 +1,4 @@ -import {detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; +import {cacheWithLocation, detectReferer, ensureProtocol, parseDomain} from 'src/refererDetection.js'; import {config} from 'src/config.js'; import {expect} from 'chai'; @@ -493,3 +493,58 @@ describe('parseDomain', () => { }) }) }); + +describe('cacheWithLocation', () => { + let fn, win, cached; + const RESULT = 'result'; + beforeEach(() => { + fn = sinon.stub().callsFake(() => RESULT); + win = { + location: { + }, + document: { + querySelector: sinon.stub() + } + } + }); + + describe('when window is not on top', () => { + beforeEach(() => { + win.top = {}; + cached = cacheWithLocation(fn, win); + }) + + it('should not cache', () => { + win.top = {}; + cached(); + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }); + }) + + describe('when window is on top', () => { + beforeEach(() => { + win.top = win; + cached = cacheWithLocation(fn, win); + }) + + it('should not cache when canonical URL changes', () => { + let canonical = 'foo'; + win.document.querySelector.callsFake(() => ({href: canonical})); + cached(); + expect(cached()).to.eql(RESULT); + canonical = 'bar'; + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }); + + it('should not cache when location changes', () => { + win.location.href = 'foo'; + cached(); + expect(cached()).to.eql(RESULT); + win.location.href = 'bar'; + expect(cached()).to.eql(RESULT); + expect(fn.callCount).to.eql(2); + }) + }); +})