Skip to content

Commit

Permalink
Refactor refererDetection to allow for URL discovery on AMP pages. (#…
Browse files Browse the repository at this point in the history
…4846)

* Refactor refererDetection to allow for URL discovery on AMP pages.

* Update import to include extension.
  • Loading branch information
mgriego authored Sep 23, 2020
1 parent 5f598b4 commit 8b9b86a
Show file tree
Hide file tree
Showing 2 changed files with 463 additions and 215 deletions.
256 changes: 117 additions & 139 deletions src/refererDetection.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,153 +10,48 @@

import { logWarn } from './utils.js';

/**
* @param {Window} win Window
* @returns {Function}
*/
export function detectReferer(win) {
/**
* Returns number of frames to reach top from current frame where prebid.js sits
* @returns {Array} levels
*/
function getLevels() {
let levels = walkUpWindows();
let ancestors = getAncestorOrigins();

if (ancestors) {
for (let i = 0, l = ancestors.length; i < l; i++) {
levels[i].ancestor = ancestors[i];
}
}
return levels;
}

/**
* This function would return a read-only array of hostnames for all the parent frames.
* win.location.ancestorOrigins is only supported in webkit browsers. For non-webkit browsers it will return undefined.
*
* @param {Window} win Window object
* @returns {(undefined|Array)} Ancestor origins or undefined
*/
function getAncestorOrigins() {
function getAncestorOrigins(win) {
try {
if (!win.location.ancestorOrigins) {
return;
}

return win.location.ancestorOrigins;
} catch (e) {
// Ignore error
}
}

/**
* This function would try to get referer and urls for all parent frames in case of win.location.ancestorOrigins undefined.
* @param {Array} levels
* @returns {Object} urls for all parent frames and top most detected referer url
*/
function getPubUrlStack(levels) {
let stack = [];
let defUrl = null;
let frameLocation = null;
let prevFrame = null;
let prevRef = null;
let ancestor = null;
let detectedRefererUrl = null;

let i;
for (i = levels.length - 1; i >= 0; i--) {
try {
frameLocation = levels[i].location;
} catch (e) {
// Ignore error
}

if (frameLocation) {
stack.push(frameLocation);
if (!detectedRefererUrl) {
detectedRefererUrl = frameLocation;
}
} else if (i !== 0) {
prevFrame = levels[i - 1];
try {
prevRef = prevFrame.referrer;
ancestor = prevFrame.ancestor;
} catch (e) {
// Ignore error
}

if (prevRef) {
stack.push(prevRef);
if (!detectedRefererUrl) {
detectedRefererUrl = prevRef;
}
} else if (ancestor) {
stack.push(ancestor);
if (!detectedRefererUrl) {
detectedRefererUrl = ancestor;
}
} else {
stack.push(defUrl);
}
} else {
stack.push(defUrl);
}
}
return {
stack,
detectedRefererUrl
};
}

/**
* 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 {
let element = doc.querySelector("link[rel='canonical']");
const element = doc.querySelector("link[rel='canonical']");

if (element !== null) {
return element.href;
}
} catch (e) {
// Ignore error
}
return null;
}

/**
* Walk up to the top of the window to detect origin, number of iframes, ancestor origins and canonical url
*/
function walkUpWindows() {
let acc = [];
let currentWindow;
do {
try {
currentWindow = currentWindow ? currentWindow.parent : win;
try {
let isTop = (currentWindow == win.top);
let refData = {
referrer: currentWindow.document.referrer || null,
location: currentWindow.location.href || null,
isTop
}
if (isTop) {
refData = Object.assign(refData, {
canonicalUrl: getCanonicalUrl(currentWindow.document)
})
}
acc.push(refData);
} catch (e) {
acc.push({
referrer: null,
location: null,
isTop: (currentWindow == win.top)
});
logWarn('Trying to access cross domain iframe. Continuing without referrer and location');
}
} catch (e) {
acc.push({
referrer: null,
location: null,
isTop: false
});
return acc;
}
} while (currentWindow != win.top);
return acc;
return null;
}

/**
Expand All @@ -170,31 +65,114 @@ export function detectReferer(win) {
*/

/**
* Get referer info
* Walk up the windows to get the origin stack and best available referrer, canonical URL, etc.
*
* @returns {refererInfo}
*/
function refererInfo() {
try {
let levels = getLevels();
let numIframes = levels.length - 1;
let reachedTop = (levels[numIframes].location !== null ||
(numIframes > 0 && levels[numIframes - 1].referrer !== null));
let stackInfo = getPubUrlStack(levels);
let canonicalUrl;
if (levels[levels.length - 1].canonicalUrl) {
canonicalUrl = levels[levels.length - 1].canonicalUrl;
const stack = [];
const ancestors = getAncestorOrigins(win);
let currentWindow;
let bestReferrer;
let bestCanonicalUrl;
let reachedTop = false;
let level = 0;
let valuesFromAmp = false;
let inAmpFrame = false;

do {
const previousWindow = currentWindow;
const wasInAmpFrame = inAmpFrame;
let currentLocation;
let crossOrigin = false;
let foundReferrer = null;

inAmpFrame = false;
currentWindow = currentWindow ? currentWindow.parent : win;

try {
currentLocation = currentWindow.location.href || null;
} catch (e) {
crossOrigin = true;
}

return {
referer: stackInfo.detectedRefererUrl,
reachedTop,
numIframes,
stack: stackInfo.stack,
canonicalUrl
};
} catch (e) {
// Ignore error
}
if (crossOrigin) {
if (wasInAmpFrame) {
const context = previousWindow.context;

try {
foundReferrer = context.sourceUrl;
bestReferrer = foundReferrer;

valuesFromAmp = true;

if (currentWindow === win.top) {
reachedTop = true;
}

if (context.canonicalUrl) {
bestCanonicalUrl = context.canonicalUrl;
}
} catch (e) { /* Do nothing */ }
} else {
logWarn('Trying to access cross domain iframe. Continuing without referrer and location');

try {
const referrer = previousWindow.document.referrer;

if (referrer) {
foundReferrer = referrer;

if (currentWindow === win.top) {
reachedTop = true;
}
}
} catch (e) { /* Do nothing */ }

if (!foundReferrer && ancestors && ancestors[level - 1]) {
foundReferrer = ancestors[level - 1];
}

if (foundReferrer && !valuesFromAmp) {
bestReferrer = foundReferrer;
}
}
} else {
if (currentLocation) {
foundReferrer = currentLocation;
bestReferrer = foundReferrer;
valuesFromAmp = false;

if (currentWindow === win.top) {
reachedTop = true;

const canonicalUrl = getCanonicalUrl(currentWindow.document);

if (canonicalUrl) {
bestCanonicalUrl = canonicalUrl;
}
}
}

if (currentWindow.context && currentWindow.context.sourceUrl) {
inAmpFrame = true;
}
}

stack.push(foundReferrer);
level++;
} while (currentWindow !== win.top);

stack.reverse();

return {
referer: bestReferrer || null,
reachedTop,
isAmp: valuesFromAmp,
numIframes: level - 1,
stack,
canonicalUrl: bestCanonicalUrl || null
};
}

return refererInfo;
Expand Down
Loading

0 comments on commit 8b9b86a

Please sign in to comment.