From 592a01e3048d9efad34e47831e2bef9f1ce67af2 Mon Sep 17 00:00:00 2001
From: Gael Robin <robin.gael@gmail.com>
Date: Wed, 10 Apr 2024 14:07:44 +0200
Subject: [PATCH] Closes #6432: Adds conditions to bail out from beacon

---
 assets/js/lcp-beacon.js                       | 27 ++++++-
 assets/js/lcp-beacon.js.min.map               |  2 +-
 assets/js/lcp-beacon.min.js                   |  2 +-
 .../Media/AboveTheFold/AJAX/Controller.php    | 30 ++++++++
 .../Media/AboveTheFold/AJAX/Subscriber.php    | 15 +++-
 .../AboveTheFold/Frontend/Controller.php      | 24 +++++++
 .../AJAX/Subscriber/checkLcpData.php          | 70 +++++++++++++++++++
 .../AJAX/Subscriber/checkLcpData.php          | 53 ++++++++++++++
 8 files changed, 218 insertions(+), 5 deletions(-)
 create mode 100644 tests/Fixtures/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php
 create mode 100644 tests/Integration/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php

diff --git a/assets/js/lcp-beacon.js b/assets/js/lcp-beacon.js
index 9357a96e355..41830ed0bed 100644
--- a/assets/js/lcp-beacon.js
+++ b/assets/js/lcp-beacon.js
@@ -19,7 +19,7 @@ function LCPCandidates(count) {
             if (imageURL !== null) {
                 // Insert element into topCandidates in descending order of area
                 for (let i = 0; i < topCandidates.length; i++) {
-                    
+
                     if (area > topCandidates[i].area) {
                         topCandidates.splice(i, 0, { element, area, imageURL });
                         topCandidates.length = Math.min(
@@ -68,6 +68,31 @@ function getImageUrlFromElement(element) {
 let performance_images = [];
 
 async function main() {
+	// AJAX call to check if there are any records for the current URL
+	const response = await fetch(rocket_lcp_data.ajax_url, {
+		method: "POST",
+		credentials: 'same-origin',
+		body: new URLSearchParams({
+			action: 'rocket_check_lcp',
+			url: rocket_lcp_data.url,
+			rocket_lcp_nonce: rocket_lcp_data.nonce
+		})
+	});
+	const lcp_data = await response.json();
+	if ( true === lcp_data.success ) {
+		console.log('Bailing out because data is already available');
+		return;
+	}
+
+	// Check screen size
+	const screenWidth = window.innerWidth || document.documentElement.clientWidth;
+	const screenHeight = window.innerHeight || document.documentElement.clientHeight;
+	if (
+		( ( screenWidth < rocket_lcp_data.width_threshold || screenHeight < rocket_lcp_data.height_threshold ) ) ) {
+		console.log('Bailing out because screen size is not acceptable');
+		return;
+	}
+
     // Filter the array based on the condition imageURL is not null
     const filteredArray = LCPCandidates(1)
     if (filteredArray.length !== 0) {
diff --git a/assets/js/lcp-beacon.js.min.map b/assets/js/lcp-beacon.js.min.map
index 5a0b4a3d6b0..7b6a7e001a7 100644
--- a/assets/js/lcp-beacon.js.min.map
+++ b/assets/js/lcp-beacon.js.min.map
@@ -1 +1 @@
-{"version":3,"names":[],"mappings":"","sources":["lcp-beacon.js"],"sourcesContent":["!function o(r,i,a){function c(t,e){if(!i[t]){if(!r[t]){var n=\"function\"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);throw(n=new Error(\"Cannot find module '\"+t+\"'\")).code=\"MODULE_NOT_FOUND\",n}n=i[t]={exports:{}},r[t][0].call(n.exports,function(e){return c(r[t][1][e]||e)},n,n.exports,o,r,i,a)}return i[t].exports}for(var l=\"function\"==typeof require&&require,e=0;e<a.length;e++)c(a[e]);return c}({1:[function(e,t,n){\"use strict\";function u(r){const e=document.querySelectorAll(\"img, video, p, main, div\"),i=[];return e.forEach(t=>{var e=t.getBoundingClientRect();if(0<e.width&&0<e.height&&0<=e.top&&e.bottom<=window.innerHeight&&0<=e.left&&e.right<=window.innerWidth){var n=e.width*e.height,o=function(e){if(\"IMG\"===e.tagName)return e.src;const t=window.getComputedStyle(e).getPropertyValue(\"background-image\");if(t&&\"none\"!==t)return t.replace(/^url\\(['\"](.+)['\"]\\)$/,\"$1\");return null}(t);if(null!==o){for(let e=0;e<i.length;e++)if(n>i[e].area){i.splice(e,0,{element:t,area:n,imageURL:o}),i.length=Math.min(r,i.length);break}i.length<r&&i.push({element:t,area:n,imageURL:o})}}}),i.map(e=>({element:e.element,imageURL:e.imageURL}))}let m=[];async function o(){const e=u(1);0!==e.length?(console.log(\"Estimated LCP element:\",e),m=e.map(e=>({src:e.imageURL,label:\"lcp\"}))):console.log(\"No LCP candidate found.\");for(var t=document.querySelectorAll(\"img\"),n=0;n<t.length;n++){var o=t[n],r=o.getBoundingClientRect();if(r.top<(window.innerHeight||document.documentElement.clientHeight)&&r.left<(window.innerWidth||document.documentElement.clientWidth)&&0<r.bottom&&0<r.right){for(var i=o.parentNode;i!==document;){var a=window.getComputedStyle(i).display,c=window.getComputedStyle(i).visibility;if(\"none\"===a||\"hidden\"===c)break;i=i.parentNode}m.some(e=>e.src===o.src)||i!==document||m.push({src:o.src,label:\"above-the-fold\"})}}console.log(m);var l=JSON.stringify(m);window.performance_images_json=l,console.log(l);const d=new FormData;d.append(\"action\",\"rocket_lcp\"),d.append(\"rocket_lcp_nonce\",rocket_lcp_data.nonce),d.append(\"url\",rocket_lcp_data.url),d.append(\"is_mobile\",rocket_lcp_data.is_mobile),d.append(\"images\",l),d.append(\"status\",\"success\"),await fetch(rocket_lcp_data.ajax_url,{method:\"POST\",credentials:\"same-origin\",body:d}).then(e=>e.json()).then(e=>{console.log(e)}).catch(e=>{console.error(e)})}\"loading\"!==document.readyState?(console.time(\"extract\"),setTimeout(o,500),console.timeEnd(\"extract\")):document.addEventListener(\"DOMContentLoaded\",async function(){console.time(\"extract\"),setTimeout(o,500),console.timeEnd(\"extract\")})},{}]},{},[1]);"],"file":"lcp-beacon.js"}
\ No newline at end of file
+{"version":3,"names":[],"mappings":"","sources":["lcp-beacon.js"],"sourcesContent":["!function o(i,c,r){function a(t,e){if(!c[t]){if(!i[t]){var n=\"function\"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);throw(n=new Error(\"Cannot find module '\"+t+\"'\")).code=\"MODULE_NOT_FOUND\",n}n=c[t]={exports:{}},i[t][0].call(n.exports,function(e){return a(i[t][1][e]||e)},n,n.exports,o,i,c,r)}return c[t].exports}for(var l=\"function\"==typeof require&&require,e=0;e<r.length;e++)a(r[e]);return a}({1:[function(e,t,n){\"use strict\";function m(i){const e=document.querySelectorAll(\"img, video, p, main, div\"),c=[];return e.forEach(t=>{var e=t.getBoundingClientRect();if(0<e.width&&0<e.height&&0<=e.top&&e.bottom<=window.innerHeight&&0<=e.left&&e.right<=window.innerWidth){var n=e.width*e.height,o=function(e){if(\"IMG\"===e.tagName)return e.src;const t=window.getComputedStyle(e).getPropertyValue(\"background-image\");if(t&&\"none\"!==t)return t.replace(/^url\\(['\"](.+)['\"]\\)$/,\"$1\");return null}(t);if(null!==o){for(let e=0;e<c.length;e++)if(n>c[e].area){c.splice(e,0,{element:t,area:n,imageURL:o}),c.length=Math.min(i,c.length);break}c.length<i&&c.push({element:t,area:n,imageURL:o})}}}),c.map(e=>({element:e.element,imageURL:e.imageURL}))}let g=[];async function o(){const e=await fetch(rocket_lcp_data.ajax_url,{method:\"POST\",credentials:\"same-origin\",body:new URLSearchParams({action:\"rocket_check_lcp\",url:rocket_lcp_data.url,rocket_lcp_nonce:rocket_lcp_data.nonce})});if(!0!==(await e.json()).success){var t=window.innerWidth||document.documentElement.clientWidth,n=window.innerHeight||document.documentElement.clientHeight;if(t<rocket_lcp_data.width_threshold||n<rocket_lcp_data.height_threshold)console.log(\"Bailing out because screen size is not acceptable\");else{const s=m(1);0!==s.length?(console.log(\"Estimated LCP element:\",s),g=s.map(e=>({src:e.imageURL,label:\"lcp\"}))):console.log(\"No LCP candidate found.\");for(var o=document.querySelectorAll(\"img\"),i=0;i<o.length;i++){var c=o[i],r=c.getBoundingClientRect();if(r.top<(window.innerHeight||document.documentElement.clientHeight)&&r.left<(window.innerWidth||document.documentElement.clientWidth)&&0<r.bottom&&0<r.right){for(var a=c.parentNode;a!==document;){var l=window.getComputedStyle(a).display,d=window.getComputedStyle(a).visibility;if(\"none\"===l||\"hidden\"===d)break;a=a.parentNode}g.some(e=>e.src===c.src)||a!==document||g.push({src:c.src,label:\"above-the-fold\"})}}console.log(g);n=JSON.stringify(g);window.performance_images_json=n,console.log(n);const u=new FormData;u.append(\"action\",\"rocket_lcp\"),u.append(\"rocket_lcp_nonce\",rocket_lcp_data.nonce),u.append(\"url\",rocket_lcp_data.url),u.append(\"is_mobile\",rocket_lcp_data.is_mobile),u.append(\"images\",n),u.append(\"status\",\"success\"),await fetch(rocket_lcp_data.ajax_url,{method:\"POST\",credentials:\"same-origin\",body:u}).then(e=>e.json()).then(e=>{console.log(e)}).catch(e=>{console.error(e)})}}else console.log(\"Bailing out because data is already available\")}\"loading\"!==document.readyState?(console.time(\"extract\"),setTimeout(o,500),console.timeEnd(\"extract\")):document.addEventListener(\"DOMContentLoaded\",async function(){console.time(\"extract\"),setTimeout(o,500),console.timeEnd(\"extract\")})},{}]},{},[1]);"],"file":"lcp-beacon.js"}
\ No newline at end of file
diff --git a/assets/js/lcp-beacon.min.js b/assets/js/lcp-beacon.min.js
index 58868a3ba2f..a1fa1861669 100644
--- a/assets/js/lcp-beacon.min.js
+++ b/assets/js/lcp-beacon.min.js
@@ -1,2 +1,2 @@
-!function o(r,i,a){function c(t,e){if(!i[t]){if(!r[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=i[t]={exports:{}},r[t][0].call(n.exports,function(e){return c(r[t][1][e]||e)},n,n.exports,o,r,i,a)}return i[t].exports}for(var l="function"==typeof require&&require,e=0;e<a.length;e++)c(a[e]);return c}({1:[function(e,t,n){"use strict";function u(r){const e=document.querySelectorAll("img, video, p, main, div"),i=[];return e.forEach(t=>{var e=t.getBoundingClientRect();if(0<e.width&&0<e.height&&0<=e.top&&e.bottom<=window.innerHeight&&0<=e.left&&e.right<=window.innerWidth){var n=e.width*e.height,o=function(e){if("IMG"===e.tagName)return e.src;const t=window.getComputedStyle(e).getPropertyValue("background-image");if(t&&"none"!==t)return t.replace(/^url\(['"](.+)['"]\)$/,"$1");return null}(t);if(null!==o){for(let e=0;e<i.length;e++)if(n>i[e].area){i.splice(e,0,{element:t,area:n,imageURL:o}),i.length=Math.min(r,i.length);break}i.length<r&&i.push({element:t,area:n,imageURL:o})}}}),i.map(e=>({element:e.element,imageURL:e.imageURL}))}let m=[];async function o(){const e=u(1);0!==e.length?(console.log("Estimated LCP element:",e),m=e.map(e=>({src:e.imageURL,label:"lcp"}))):console.log("No LCP candidate found.");for(var t=document.querySelectorAll("img"),n=0;n<t.length;n++){var o=t[n],r=o.getBoundingClientRect();if(r.top<(window.innerHeight||document.documentElement.clientHeight)&&r.left<(window.innerWidth||document.documentElement.clientWidth)&&0<r.bottom&&0<r.right){for(var i=o.parentNode;i!==document;){var a=window.getComputedStyle(i).display,c=window.getComputedStyle(i).visibility;if("none"===a||"hidden"===c)break;i=i.parentNode}m.some(e=>e.src===o.src)||i!==document||m.push({src:o.src,label:"above-the-fold"})}}console.log(m);var l=JSON.stringify(m);window.performance_images_json=l,console.log(l);const d=new FormData;d.append("action","rocket_lcp"),d.append("rocket_lcp_nonce",rocket_lcp_data.nonce),d.append("url",rocket_lcp_data.url),d.append("is_mobile",rocket_lcp_data.is_mobile),d.append("images",l),d.append("status","success"),await fetch(rocket_lcp_data.ajax_url,{method:"POST",credentials:"same-origin",body:d}).then(e=>e.json()).then(e=>{console.log(e)}).catch(e=>{console.error(e)})}"loading"!==document.readyState?(console.time("extract"),setTimeout(o,500),console.timeEnd("extract")):document.addEventListener("DOMContentLoaded",async function(){console.time("extract"),setTimeout(o,500),console.timeEnd("extract")})},{}]},{},[1]);
+!function o(i,c,r){function a(t,e){if(!c[t]){if(!i[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(l)return l(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=c[t]={exports:{}},i[t][0].call(n.exports,function(e){return a(i[t][1][e]||e)},n,n.exports,o,i,c,r)}return c[t].exports}for(var l="function"==typeof require&&require,e=0;e<r.length;e++)a(r[e]);return a}({1:[function(e,t,n){"use strict";function m(i){const e=document.querySelectorAll("img, video, p, main, div"),c=[];return e.forEach(t=>{var e=t.getBoundingClientRect();if(0<e.width&&0<e.height&&0<=e.top&&e.bottom<=window.innerHeight&&0<=e.left&&e.right<=window.innerWidth){var n=e.width*e.height,o=function(e){if("IMG"===e.tagName)return e.src;const t=window.getComputedStyle(e).getPropertyValue("background-image");if(t&&"none"!==t)return t.replace(/^url\(['"](.+)['"]\)$/,"$1");return null}(t);if(null!==o){for(let e=0;e<c.length;e++)if(n>c[e].area){c.splice(e,0,{element:t,area:n,imageURL:o}),c.length=Math.min(i,c.length);break}c.length<i&&c.push({element:t,area:n,imageURL:o})}}}),c.map(e=>({element:e.element,imageURL:e.imageURL}))}let g=[];async function o(){const e=await fetch(rocket_lcp_data.ajax_url,{method:"POST",credentials:"same-origin",body:new URLSearchParams({action:"rocket_check_lcp",url:rocket_lcp_data.url,rocket_lcp_nonce:rocket_lcp_data.nonce})});if(!0!==(await e.json()).success){var t=window.innerWidth||document.documentElement.clientWidth,n=window.innerHeight||document.documentElement.clientHeight;if(t<rocket_lcp_data.width_threshold||n<rocket_lcp_data.height_threshold)console.log("Bailing out because screen size is not acceptable");else{const s=m(1);0!==s.length?(console.log("Estimated LCP element:",s),g=s.map(e=>({src:e.imageURL,label:"lcp"}))):console.log("No LCP candidate found.");for(var o=document.querySelectorAll("img"),i=0;i<o.length;i++){var c=o[i],r=c.getBoundingClientRect();if(r.top<(window.innerHeight||document.documentElement.clientHeight)&&r.left<(window.innerWidth||document.documentElement.clientWidth)&&0<r.bottom&&0<r.right){for(var a=c.parentNode;a!==document;){var l=window.getComputedStyle(a).display,d=window.getComputedStyle(a).visibility;if("none"===l||"hidden"===d)break;a=a.parentNode}g.some(e=>e.src===c.src)||a!==document||g.push({src:c.src,label:"above-the-fold"})}}console.log(g);n=JSON.stringify(g);window.performance_images_json=n,console.log(n);const u=new FormData;u.append("action","rocket_lcp"),u.append("rocket_lcp_nonce",rocket_lcp_data.nonce),u.append("url",rocket_lcp_data.url),u.append("is_mobile",rocket_lcp_data.is_mobile),u.append("images",n),u.append("status","success"),await fetch(rocket_lcp_data.ajax_url,{method:"POST",credentials:"same-origin",body:u}).then(e=>e.json()).then(e=>{console.log(e)}).catch(e=>{console.error(e)})}}else console.log("Bailing out because data is already available")}"loading"!==document.readyState?(console.time("extract"),setTimeout(o,500),console.timeEnd("extract")):document.addEventListener("DOMContentLoaded",async function(){console.time("extract"),setTimeout(o,500),console.timeEnd("extract")})},{}]},{},[1]);
 //# sourceMappingURL=lcp-beacon.js.map
diff --git a/inc/Engine/Media/AboveTheFold/AJAX/Controller.php b/inc/Engine/Media/AboveTheFold/AJAX/Controller.php
index 6507efc538f..f8d9c1257e0 100644
--- a/inc/Engine/Media/AboveTheFold/AJAX/Controller.php
+++ b/inc/Engine/Media/AboveTheFold/AJAX/Controller.php
@@ -91,4 +91,34 @@ public function add_lcp_data() {
 
 		wp_send_json_success( $item );
 	}
+
+	/**
+	 * Checks if there is existing LCP data for the current URL and device type.
+	 *
+	 * This method is called via AJAX. It checks if there is existing LCP data for the current URL and device type.
+	 * If the data exists, it returns a JSON success response with true. If the data does not exist, it returns a JSON success response with false.
+	 * If the context is not allowed, it returns a JSON error response with false.
+	 *
+	 * @return void
+	 */
+	public function check_lcp_data() {
+		check_ajax_referer( 'rocket_lcp', 'rocket_lcp_nonce' );
+
+		if ( ! $this->context->is_allowed() ) {
+			wp_send_json_error( false );
+			return;
+		}
+
+		$url       = isset( $_POST['url'] ) ? untrailingslashit( esc_url_raw( wp_unslash( $_POST['url'] ) ) ) : '';
+		$is_mobile = isset( $_POST['is_mobile'] ) ? filter_var( wp_unslash( $_POST['is_mobile'] ), FILTER_VALIDATE_BOOL ) : false;
+
+		$row = $this->query->get_row( $url, $is_mobile );
+
+		if ( ! empty( $row ) ) {
+			wp_send_json_success( 'data already exists' );
+			return;
+		}
+
+		wp_send_json_error( 'data does not exist' );
+	}
 }
diff --git a/inc/Engine/Media/AboveTheFold/AJAX/Subscriber.php b/inc/Engine/Media/AboveTheFold/AJAX/Subscriber.php
index ba8769aa237..6a070c454b0 100644
--- a/inc/Engine/Media/AboveTheFold/AJAX/Subscriber.php
+++ b/inc/Engine/Media/AboveTheFold/AJAX/Subscriber.php
@@ -29,8 +29,10 @@ public function __construct( Controller $controller ) {
 	 */
 	public static function get_subscribed_events() {
 		return [
-			'wp_ajax_rocket_lcp'        => 'add_lcp_data',
-			'wp_ajax_nopriv_rocket_lcp' => 'add_lcp_data',
+			'wp_ajax_rocket_lcp'              => 'add_lcp_data',
+			'wp_ajax_nopriv_rocket_lcp'       => 'add_lcp_data',
+			'wp_ajax_rocket_check_lcp'        => 'check_lcp_data',
+			'wp_ajax_nopriv_rocket_check_lcp' => 'check_lcp_data',
 		];
 	}
 
@@ -42,4 +44,13 @@ public static function get_subscribed_events() {
 	public function add_lcp_data() {
 		$this->controller->add_lcp_data();
 	}
+
+	/**
+	 * Callback for checking lcp data
+	 *
+	 * @return void
+	 */
+	public function check_lcp_data() {
+		$this->controller->check_lcp_data();
+	}
 }
diff --git a/inc/Engine/Media/AboveTheFold/Frontend/Controller.php b/inc/Engine/Media/AboveTheFold/Frontend/Controller.php
index 88303e955bb..98be4da9abe 100644
--- a/inc/Engine/Media/AboveTheFold/Frontend/Controller.php
+++ b/inc/Engine/Media/AboveTheFold/Frontend/Controller.php
@@ -350,11 +350,35 @@ public function inject_beacon( $html, $url, $is_mobile ): string {
 			return $html;
 		}
 
+		/**
+		 * Filters the width threshold for the LCP beacon.
+		 *
+		 * @param int    $width_threshold The width threshold. Default is 393 for mobile and 1920 for others.
+		 * @param bool   $is_mobile       True if the current device is mobile, false otherwise.
+		 * @param string $url             The current URL.
+		 *
+		 * @return int The filtered width threshold.
+		 */
+		$width_threshold = apply_filters('rocket_lcp_width_threshold', ($is_mobile ? 393 : 1920 ), $is_mobile, $url );
+
+		/**
+		 * Filters the height threshold for the LCP beacon.
+		 *
+		 * @param int    $height_threshold The height threshold. Default is 830 for mobile and 1080 for others.
+		 * @param bool   $is_mobile        True if the current device is mobile, false otherwise.
+		 * @param string $url              The current URL.
+		 *
+		 * @return int The filtered height threshold.
+		 */
+		$height_threshold = apply_filters('rocket_lcp_height_threshold', ($is_mobile ? 830 : 1080 ), $is_mobile, $url );
+
 		$data = [
 			'ajax_url'  => admin_url( 'admin-ajax.php' ),
 			'nonce'     => wp_create_nonce( 'rocket_lcp' ),
 			'url'       => $url,
 			'is_mobile' => $is_mobile,
+			'width_threshold' => $width_threshold,
+			'height_threshold' => $height_threshold,
 		];
 
 		$inline_script = '<script>var rocket_lcp_data = ' . wp_json_encode( $data ) . '</script>';
diff --git a/tests/Fixtures/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php b/tests/Fixtures/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php
new file mode 100644
index 00000000000..61a65ae4b2d
--- /dev/null
+++ b/tests/Fixtures/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php
@@ -0,0 +1,70 @@
+<?php
+
+return [
+	'testShouldReturnErrorWhenNotAllowed' => [
+		'config'   => [
+			'filter'    => false,
+			'url'       => 'http://example.org',
+			'is_mobile' => false,
+			'post'      => [
+				'rocket_lcp_nonce' => wp_create_nonce( 'rocket_lcp' ),
+				'action'           => 'rocket_check_lcp',
+				'url'              => 'http://example.org',
+				'is_mobile'        => false,
+			],
+			'row' => [],
+		],
+		'expected' => [
+			'result'  => false,
+		],
+	],
+	'testShouldReturnExists' => [
+		'config'   => [
+			'filter'    => true,
+			'url'       => 'http://example.org',
+			'is_mobile' => false,
+			'post'      => [
+				'rocket_lcp_nonce' => wp_create_nonce( 'rocket_lcp' ),
+				'action'           => 'rocket_check_lcp',
+				'url'              => 'http://example.org',
+				'is_mobile'        => false,
+			],
+			'row' => [
+				'status' => 'completed',
+				'url' => 'http://example.org',
+				'lcp'      => json_encode( (object) [
+					'type' => 'img',
+					'src'  => 'http://example.org/wp-content/uploads/image.jpg',
+				] ),
+				'viewport' => json_encode( [
+					0 => (object) [
+						'type' => 'img',
+						'src'  => 'http://example.org/wp-content/uploads/image.jpg',
+					],
+				] ),
+			],
+			'row_exists' => true,
+		],
+		'expected' => [
+			'result'  => true,
+		],
+	],
+	'testShouldReturnNotExists' => [
+		'config'   => [
+			'filter'    => true,
+			'url'       => 'http://example.org',
+			'is_mobile' => false,
+			'post'      => [
+				'rocket_lcp_nonce' => wp_create_nonce( 'rocket_lcp' ),
+				'action'           => 'rocket_check_lcp',
+				'url'              => 'http://example.org',
+				'is_mobile'        => false,
+			],
+			'row' => [],
+			'row_exists' => false,
+		],
+		'expected' => [
+			'result'  => false,
+		],
+	],
+];
diff --git a/tests/Integration/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php b/tests/Integration/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php
new file mode 100644
index 00000000000..c8ea52f3634
--- /dev/null
+++ b/tests/Integration/inc/Engine/Media/AboveTheFold/AJAX/Subscriber/checkLcpData.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace WP_Rocket\Tests\Integration\Inc\Engine\Media\AboveTheFold\AJAX\Subscriber;
+
+use WP_Rocket\Tests\Integration\AjaxTestCase;
+
+/**
+ * @covers WP_Rocket\Engine\Media\AboveTheFold\AJAX\Subscriber::check_lcp_data
+ *
+ * @group AboveTheFold
+ */
+class Test_CheckLcpData extends AjaxTestCase {
+	private $allowed;
+
+	public function set_up() {
+		parent::set_up();
+
+		$this->action = 'rocket_check_lcp';
+	}
+
+	/**
+	 * $_POST is cleared in parent method
+	 *
+	 * @return void
+	 */
+	public function tear_down() {
+		remove_filter( 'rocket_above_the_fold_optimization', [ $this, 'set_allowed' ] );
+
+		parent::tear_down();
+	}
+
+	/**
+	 * @dataProvider configTestData
+	 */
+	public function testShouldReturnExpected( $config, $expected ) {
+		$_POST = $config['post'];
+
+		$this->allowed = $config['filter'];
+
+		add_filter( 'rocket_above_the_fold_optimization', [ $this, 'set_allowed' ] );
+
+		if ( ! empty( $config['row'] ) ) {
+			self::addLcp( $config['row'] );
+		}
+
+		$result = $this->callAjaxAction();
+
+		$this->assertSame( $expected['result'], $result->success );
+	}
+	public function set_allowed() {
+		return $this->allowed;
+	}
+}