How content recommendation provider Taboola used LoAF to improve INP up to 36% for its publisher partner websites.

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.

David Belford
David Belford

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.

A screenshot of a Lighthouse audit for blocked main thread time. The main thread was blocked in total by several scripts for 2,630 milliseconds, with third-party JavaScript contributing 712 milliseconds to that time. Taboola's RELEASE.js script is responsible for the majority of third-party blocking time at 691 milliseconds.
With Taboola's old engine, scripts like RELEASE.js block the main thread for 691 milliseconds.

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:

  1. Attribute entries to specific Taboola tasks.
  2. Observe performance issues in specific features before they are deployed to production.
  3. 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 a firstUIEventTimeStamp 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.

Script Duration (milliseconds)
vpaid/units/33_6_8/infra/cmTagINLINE_INSTREAM.js:106517 997
vpaid/units/33_6_8/infra/cmTagFEED_MANAGER.js:496662 561
vpaid/vPlayer/player/v15.8.6/OvaMediaPlayer.js:44631 336
libtrc/impl.20231212-23-RELEASE.js:821090 857
publisher_name/pmk-20220605.5.js:7728 336
libtrc/card-interference-detector.20231219-7-RELEASE.es6.js:183 239
LoAF script entries captured by Taboola RUM

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 uses scheduler.postTask() under the hood (if the API is available), or falls back to setTimeout.
  • 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.

Publishers INP with TRECS + Performance Fader INP with the existing Engine INP decrease (%)
Publisher A 48 75 36%
Publisher B 153 163 6%
Publisher C 92 135 33%
Publisher D 37 52 29%

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.

A screenshot of a Lighthouse audit for blocked main thread time after the new TRECS and Performance Fader engines were applied to improve main thread blocking time. The audit was reduced to only 206 milliseconds, compared to 712 before optimizations were made.
Taboola's new engine helped scripts like RELEASE.js to reduce TBT by 485 ms (-70%).

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.