Skip to content

Commit

Permalink
Add LRC functionality (#20)
Browse files Browse the repository at this point in the history
* start adding the beaconlrc class
* finalize the beacon manager class
* temporarily fix tests
* Return only hash as payload
* Remove duplicate payload
* Only add child element below the fold threshold and parent elements only below the fold
* use config threshold instead of static value
* Revert "use config threshold instead of static value"
This reverts commit b4f633b.
* use logger class and comment the xpath for now
* Replace static values with config
* start doing as grooming
* get only elements that have hash
* Added test for lrc
* Best practices
* Additional best practices
* start considering viewport on element distance calculation
* adjust imports and the log message
* change the structure of test to handle more cases and use sinon for mocking
* test
* test 2
* fix tests attempt 1
* add more tests
* add tests for run method
* add more tests
* remove not used argument
---------

Co-authored-by: Michael Lee <[email protected]>
Co-authored-by: Mathieu Lamiot <[email protected]>
  • Loading branch information
3 people authored Sep 16, 2024
1 parent ba590ed commit 8e25bbf
Show file tree
Hide file tree
Showing 6 changed files with 412 additions and 8 deletions.
143 changes: 143 additions & 0 deletions src/BeaconLrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use strict';

import BeaconUtils from "./Utils.js";

class BeaconLrc {
constructor(config, logger) {
this.config = config;
this.logger = logger;
this.lazyRenderElements = [];
}

async run() {
try {
const elementsInView = this._getLazyRenderElements();
if (elementsInView) {
this._processElements(elementsInView);
}
} catch (err) {
this.errorCode = 'script_error';
this.logger.logMessage('Script Error: ' + err);
}
}

_getLazyRenderElements() {
const elements = document.querySelectorAll('[data-rocket-location-hash]');

if (elements.length <= 0) {
return [];
}

const validElements = Array.from(elements).filter(element => !this._skipElement(element));

return validElements.map(element => ({
element: element,
depth: this._getElementDepth(element),
distance: this._getElementDistance(element),
hash: this._getLocationHash(element)
}));
}

_getElementDepth(element) {
let depth = 0;
let parent = element.parentElement;
while (parent) {
depth++;
parent = parent.parentElement;
}
return depth;
}

_getElementDistance(element) {
const rect = element.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
return Math.max(0, rect.top + scrollTop - BeaconUtils.getScreenHeight());
}

_skipElement(element) {
const skipStrings = this.config.skipStrings || ['memex'];
if (!element || !element.id) return false;
return skipStrings.some(str => element.id.toLowerCase().includes(str.toLowerCase()));
}

_shouldSkipElement(element, exclusions) {
if (!element) return false;
for (let i = 0; i < exclusions.length; i++) {
const [attribute, pattern] = exclusions[i];
const attributeValue = element.getAttribute(attribute);
if (attributeValue && new RegExp(pattern, 'i').test(attributeValue)) {
return true;
}
}
return false;
}

_processElements(elements) {
elements.forEach(({ element, depth, distance, hash }) => {
if (this._shouldSkipElement(element, this.config.exclusions || [])) {
return;
}

if ( 'No hash detected' === hash ) {
return;
}

const can_push_hash = element.parentElement && this._getElementDistance(element.parentElement) < this.config.lrc_threshold && distance >= this.config.lrc_threshold;

const color = can_push_hash ? "green" : distance === 0 ? "red" : "";
this.logger.logColoredMessage( `${'\t'.repeat(depth)}${element.tagName} (Depth: ${depth}, Distance from viewport bottom: ${distance}px)`, color );

//const xpath = this._getXPath(element);
//console.log(`%c${'\t'.repeat(depth)}Xpath: ${xpath}`, style);

this.logger.logColoredMessage(`${'\t'.repeat(depth)}Location hash: ${hash}`, color);

this.logger.logColoredMessage(`${'\t'.repeat(depth)}Dimensions Client Height: ${element.clientHeight}`, color);

if (can_push_hash) {
this.lazyRenderElements.push(hash); // Push the hash
this.logger.logMessage(`Element pushed with hash: ${hash}`);
}
});
}

_getXPath(element) {
if (element && element.id !== "") {
return `//*[@id="${element.id}"]`;
}

return this._getElementXPath(element);
}

_getElementXPath(element) {
if (element === document.body) {
return '/html/body';
}
const position = this._getElementPosition(element);
return `${this._getElementXPath(element.parentNode)}/${element.nodeName.toLowerCase()}[${position}]`;
}

_getElementPosition(element) {
let pos = 1;
let sibling = element.previousElementSibling;
while (sibling) {
if (sibling.nodeName === element.nodeName) {
pos++;
}
sibling = sibling.previousElementSibling;
}
return pos;
}

_getLocationHash(element) {
return element.hasAttribute('data-rocket-location-hash')
? element.getAttribute('data-rocket-location-hash')
: 'No hash detected';
}

getResults() {
return this.lazyRenderElements;
}
}

export default BeaconLrc;
21 changes: 16 additions & 5 deletions src/BeaconManager.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
'use strict';

import BeaconLcp from "./BeaconLcp.js";
import BeaconLrc from "./BeaconLrc.js";
import BeaconUtils from "./Utils.js";
import Logger from "./Logger.js";

class BeaconManager {
constructor(config) {
this.config = config;
this.lcpBeacon = null;
this.lrcBeacon = null;
this.infiniteLoopId = null;
this.errorCode = '';
this.logger = new Logger(this.config.debug);
Expand All @@ -25,21 +27,29 @@ class BeaconManager {
}, 10000);

const isGeneratedBefore = await this._getGeneratedBefore();
let shouldSaveResultsIntoDB = false;

// OCI / LCP / ATF
const shouldGenerateLcp = (
this.config.status.atf && (isGeneratedBefore === false || isGeneratedBefore.lcp === false)
);
const shouldGeneratelrc = (
this.config.status.lrc && (isGeneratedBefore === false || isGeneratedBefore.lrc === false)
);
if (shouldGenerateLcp) {
this.lcpBeacon = new BeaconLcp(this.config, this.logger);
await this.lcpBeacon.run();
shouldSaveResultsIntoDB = true;
} else {
this.logger.logMessage('Not running BeaconLcp because data is already available');
this.logger.logMessage('Not running BeaconLcp because data is already available or feature is disabled');
}

if (shouldGeneratelrc) {
this.lrcBeacon = new BeaconLrc(this.config, this.logger);
await this.lrcBeacon.run();
} else {
this.logger.logMessage('Not running BeaconLrc because data is already available or feature is disabled');
}

if (shouldSaveResultsIntoDB) {
if (shouldGenerateLcp || shouldGeneratelrc) {
this._saveFinalResultIntoDB();
} else {
this.logger.logMessage("Not saving results into DB as no beacon features ran.");
Expand Down Expand Up @@ -84,7 +94,8 @@ class BeaconManager {

_saveFinalResultIntoDB() {
const results = {
lcp: this.lcpBeacon ? this.lcpBeacon.getResults() : null
lcp: this.lcpBeacon ? this.lcpBeacon.getResults() : null,
lrc: this.lrcBeacon ? this.lrcBeacon.getResults() : null
};

const data = new FormData();
Expand Down
7 changes: 7 additions & 0 deletions src/Logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ class Logger {
}
console.log(msg);
}

logColoredMessage( msg, color = 'green' ) {
if (!this.enabled) {
return;
}
console.log(`%c${msg}`, `color: ${color};`);
}
}

export default Logger;
12 changes: 10 additions & 2 deletions src/Utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
'use strict';

class BeaconUtils {
static getScreenWidth() {
return window.innerWidth || document.documentElement.clientWidth;
}

static getScreenHeight() {
return window.innerHeight || document.documentElement.clientHeight;
}

static isNotValidScreensize( is_mobile, threshold ) {
const screenWidth = window.innerWidth || document.documentElement.clientWidth;
const screenHeight = window.innerHeight || document.documentElement.clientHeight;
const screenWidth = this.getScreenWidth();
const screenHeight = this.getScreenHeight();

const isNotValidForMobile = is_mobile &&
(screenWidth > threshold.width || screenHeight > threshold.height);
Expand Down
Loading

0 comments on commit 8e25bbf

Please sign in to comment.