Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: cache refererInfo as long as location does not change #9670

Merged
merged 1 commit into from
Apr 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 39 additions & 21 deletions src/refererDetection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <head> 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}
Expand All @@ -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 <head> 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).
Expand Down Expand Up @@ -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));
57 changes: 56 additions & 1 deletion test/spec/refererDetection_spec.js
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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);
})
});
})