How leveraging the Long Animation Frames API (LoAF) and adopting a smart yielding strategy enabled Taboola to improve publishers' website responsiveness without compromising ad performance.
Interaction to Next Paint (INP) is a metric that assesses a website's responsiveness to user input. INP measures the time from when a user begins an interaction—such as when clicking, tapping, or typing—to the visual feedback that results. INP is due to replace First Input Delay (FID) as a Core Web Vital in March 2024.
Taboola is the world's leading content discovery platform, powering 500,000 recommendations every second on the open web. These recommendations enable Taboola's 9,000 exclusive publisher partners to monetize and engage their audiences. Publishers render recommendations on their pages using Javascript.
Since third-party JavaScript can affect a page's ability to respond quickly to user input, Taboola has been heavily invested in reducing its JavaScript files sizes and execution time. Taboola has been redesigning its entire rendering engine, as well as using browser APIs directly without abstractions to minimize its impact on INP.
This case study covers Taboola's journey to improve INP by using the new Long Animation Frames (LoAF) API to measure its impact on page responsiveness in the field, and subsequent efforts to apply specific optimisations to improve the user experience.
TBT as a proxy of INP
Total Blocking Time (TBT) is a lab-based metric which identifies where the main thread was blocked for long enough to likely affect page responsiveness. Field metrics that measure responsiveness—such as INP—can be affected by a high TBT. An investigation by Annie Sullivan into the correlation between TBT and INP on mobile devices indicates that sites are more likely to achieve good INP scores when main thread blocking time is minimized.
This correlation, coupled with Taboola's publishers' concerns around high TBT, led Taboola to focus its attention on minimizing its contribution to this metric.
Using TBT as a proxy metric for INP, Taboola began monitoring and optimizing JavaScript execution time to limit its potential impact on Core Web Vitals. They started by doing the following:
- Identifying and optimizing problematic scripts in the field using the Long Tasks API.
- Estimating TBT contributions by using the PageSpeed Insights API to evaluate 10,000 to 15,000 URLs each day.
However, Taboola noticed that analyzing TBT with these tools had some limitations:
- The Long Tasks API cannot attribute the task to the origin domain or a particular script, making it more difficult to identify sources of long tasks.
- The Long Tasks API only identifies long tasks, rather than a combination of tasks and layout changes that could cause a rendering delay.
To tackle these challenges, Taboola joined the Long Animation Frames (LoAF) API origin trial in an effort to better understand its real impact on user input responsiveness. Origin trials give access to new or experimental features, allowing developers to test emerging features that their users can try out for a limited time.
It is essential to highlight that the most difficult aspect of this challenge was to successfully improve INP without compromising any Ads KPI(Key Performance Indicator) or causing resource delays for our publishers.
Using LoAF to assess INP impact
A long animation frame occurs when a rendering update is delayed beyond 50 milliseconds. By identifying causes of slow user interface updates—rather than long tasks alone—Taboola was able to analyze its impact on page responsiveness in the field. Observing LoAF has allowed Taboola to:
- Attribute entries to specific Taboola tasks.
- Observe performance issues in specific features before they are deployed to production.
- Gather aggregated data to compare different code versions in A/B tests, and report on key success metrics.
The following JavaScript is a simplified version used in production for collecting LoAF to isolate Taboola's impact.
function loafEntryAnalysis (entry) {
if (entry.blockingDuration === 0) {
return;
}
let taboolaIsMajor = false;
const hasInteraction = entry.firstUIEventTimestamp > 0;
let taboolaDuration = 0;
const nonTaboolaLoafReport = {};
const taboolaLoafReport = {};
entry.scripts.forEach((script) => {
const taboolaScriptBlockingDuration = handleLongAnimationFrameScript(script, taboolaLoafReport, nonTaboolaLoafReport);
taboolaDuration += taboolaScriptBlockingDuration;
if (taboolaScriptBlockingDuration > 0 || taboolaDuration > entry.duration / 2) {
taboolaIsMajor = true;
}
});
generateToboolaLoafReport(taboolaLoafReport, nonTaboolaLoafReport, hasInteraction, taboolaIsMajor);
if (hasInteraction) {
const global = _longAnimationFramesReport.global;
global.inpBlockingDuration = Math.max(global.inpBlockingDuration, entry.blockingDuration);
if (taboolaIsMajor) {
global.taboolaInpBlockingDuration = Math.max(global.taboolaInpBlockingDuration, entry.blockingDuration);
}
}
}
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) {
loafEntryAnalysis(entry);
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
- Using the
loafEntryAnalysis
function allowed Taboola to identify entries where it is a major contributor. - Taboola is considered a major contributor if more than half of the total script duration is caused by Taboola, or a Taboola script takes more than 50 milliseconds to run.
- A
firstUIEventTimeStamp
is generated if user interaction is delayed due to a Long Animation Frame. The longest blocking duration is considered as the overall INP score. We can also identify when Taboola has triggered afirstUIEventTimeStamp
to calculate a Taboola INP score.
Data collected with LoAF helped Taboola create the following attribution table, which identifies areas where it can apply yielding opportunities.
TRECS Engine: the new yielding strategy
As well as using LoAF to better understand script optimisation opportunities, Taboola has been redesigning its entire rendering engine to significantly minimize Javascript execution and blocking time.
TRECS (Taboola Recommendations Extensible Client Service) maintains client-side rendering and the publisher's current JS code while reducing the number and size of mandatory files required to load Taboola's recommendations.
Once render blocking tasks have been identified using LoAF, the "Performance Fader" can break up those tasks before yielding to the main thread using scheduler.postTask()
. This design ensures that crucial user-facing work—such as rendering updates—can be executed as soon as possible regardless of any existing tasks that may be occupying the main thread.
Here is the JS snippet of the "Performance Fader" task runner:
/**
* Send a task to run using the Fader. The task will run using the browser Scheduler, by the configuration settings, or immediately.
* @param task
* @param isBlocker
*/
function sendTaskToFader (task, isBlocker = true) {
const publisherFaderChoice = fillOptimizationGlobals(); // Loading publisher choice
const applyYielding = publisherFaderChoice === OptimizationFaderType.Responsiveness;
if (applyYielding) {
return runAsPostTask(task, isBlocker);
}
return runImmediately(task);
}
/**
* Yielding method using scheduler.postTask and falling back to setTimeout when it's not availabe based on the publisher choice
*/
function runAsPostTask (task, isBlocker = true) {
if ('scheduler' in window && 'postTask' in scheduler) {
const priority = isBlocker ? 'user-blocking': 'background';
return window?.scheduler?.postTask(task, { priority });
}
const publisherChoiceEnableFallback = fillPublisherChoices();
if (publisherChoiceEnableFallback) {
return new Promise(resolve => {
window.setTimeout(() => {
resolve(task());
}, 0);
});
}
return runImmediately(task);
}
The sendTaskToFader
function:
- Uses
runAsPostTask
, which usesscheduler.postTask()
under the hood (if the API is available), or falls back tosetTimeout
. - This function wraps function calls in code sections that cause long animation frames and INP. It splits these code sections into shorter tasks, and thus reduces INP.
Business metrics
Thanks to LoAF, Taboola was able to better understand its impact on INP. The tool also highlighted script optimization opportunities that could be used as part of the new TRECS engine.
To determine the impact of TRECS and the Performance Fader, Taboola conducted an A/B test measuring INP against the existing engine with no script yielding on a panel of publisher partners.
The following table shows INP results in milliseconds at the 75th percentile of four anonymous publishers in the Taboola network.
Fortunately, business metrics, such as Ad Click-through Rate and Revenue per 1,000 impressions (RPM), were not negatively impacted when TRECS and the Performance Fader were enabled on the testing panel. With this positive improvement on INP without any negative result as expected on the Ads KPIs, Taboola will gradually improve its publishers' perception about its product.
Another Lighthouse run on the same customer highlighted earlier demonstrates a significant improvement in the main thread blocking time by Taboola when using the new engine.
This demonstrates that using LoAF to identify causes of INP and deploying the subsequent yielding techniques with the Performance Fader enables Taboola's partners to achieve maximum success in ad and page performance.
Conclusion
Optimizing INP is a complex process, particularly when third-party scripts are used on partner websites. Before optimization can begin, attribution of INP to specific scripts removes any guesswork and potential damage to other site performance metrics.The LoAF API has proven to be a valuable tool for identifying and addressing INP issues particularly for 3Ps embedded by allowing them to pinpoint their specific SDK improvement opportunities while eliminating interference from other technologies present on the page.
When used in conjunction with a good yielding strategy—such as using scheduler.postTask()
—LoAF can help you to observe and understand the cause of poor page responsiveness, which in turn, gives you the information you need to improve your website's INP.
Special thanks to Gilberto Cocchi, Noam Rosenthal, and Rick Viscomi from Google, and Dedi Hakak, Anat Dagan and Omri Ariav from Taboola's Engineering and Product team for their contribution to this work.