Repository: prebid/Prebid.js
Pull request #12848: getBoundingClientRect and percent Inview library: replace layout calculations in various adapters
Author: login: mkomorski
Labels: name: feature
Created: 2025-03-07T13:23:08Z | Merged: 2025-03-13T16:57:51Z

<!--
Thank you for your pull request! 

Please title your pull request like this: 'Module: Change', eg 'Fraggles Bid Adapter: support fragglerock'

Please make sure this PR is scoped to one change or you may be asked to resubmit. 
 
Please make sure any added or changed code includes tests with greater than 80% code coverage. 

See https://github.com/prebid/Prebid.js/blob/master/CONTRIBUTING.md#testing-prebidjs for documentation on testing Prebid.js.

For any user facing change, submit a link to a PR on the docs repo at https://github.com/prebid/prebid.github.io/
-->

## Type of change
<!-- Remove items that don't apply and/or select an item by changing [ ] to [x] -->
- [ ] Bugfix
- [x] Feature
- [ ] New bidder adapter  <!--  IMPORTANT: also submit your bidder parameter documentation as noted in https://docs.prebid.org/dev-docs/bidder-adaptor.html#submitting-your-adapter -->
- [ ] Updated bidder adapter  <!--  IMPORTANT: (1) consider whether you need to upgrade your bidder parameter documentation in https://github.com/prebid/prebid.github.io/tree/master/dev-docs/bidders and (2) if you have a Prebid Server adapter, please consider whether that should be updated as well. --> 
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes

- [ ] Does this change affect user-facing APIs or examples documented on http://prebid.org?
- [ ] Other

## Description of change
<!-- Describe the change proposed in this pull request -->

<!-- For new bidder adapters, please provide the following
- contact email of the adapter’s maintainer
- test parameters for validating bids:
```
{
  bidder: '<bidder name>',
  params: {
    // ...
  }
}
```

Be sure to test the integration with your adserver using the [Hello World](https://github.com/prebid/Prebid.js/blob/master/integrationExamples/gpt/hello_world.html) sample page. -->


## Other information
<!-- References to related PR or issue #s, @mentions of the person or team responsible for reviewing changes, etc. -->
#11963 


Diff:
diff --git a/.eslintrc.js b/.eslintrc.js
index 5b69afa019f..661ba8bb5fd 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -131,6 +131,12 @@ module.exports = {
             '**/src/adloader.js'
           ]
         }
+      ],
+      'no-restricted-properties': [
+        'error', {
+          property: "getBoundingClientRect",
+          message: "Please use libraries/boundingClientRect getBoundingClientRect fn instead"
+        }
       ]
     }
   }])
diff --git a/libraries/boundingClientRect/boundingClientRect.js b/libraries/boundingClientRect/boundingClientRect.js
new file mode 100644
index 00000000000..e8de77c4de8
--- /dev/null
+++ b/libraries/boundingClientRect/boundingClientRect.js
@@ -0,0 +1,24 @@
+import { startAuction } from '../../src/prebid.js';
+
+const cache = new Map();
+
+startAuction.before((next, auctionConfig) => {
+  clearCache();
+  next(auctionConfig);
+});
+
+export function clearCache() {
+  cache.clear();
+}
+
+export function getBoundingClientRect(element) {
+  let clientRect;
+  if (cache.has(element)) {
+    clientRect = cache.get(element);
+  } else {
+    clientRect = element.getBoundingClientRect();
+    cache.set(element, clientRect);
+  }
+
+  return clientRect;
+}
diff --git a/libraries/percentInView/percentInView.js b/libraries/percentInView/percentInView.js
index 13381c5c541..f1d17f933df 100644
--- a/libraries/percentInView/percentInView.js
+++ b/libraries/percentInView/percentInView.js
@@ -1,6 +1,7 @@
+import { getBoundingClientRect } from '../boundingClientRect/boundingClientRect.js';
 
-function getBoundingBox(element, {w, h} = {}) {
-  let {width, height, left, top, right, bottom} = element.getBoundingClientRect();
+export function getBoundingBox(element, {w, h} = {}) {
+  let {width, height, left, top, right, bottom, x, y} = getBoundingClientRect(element);
 
   if ((width === 0 || height === 0) && w && h) {
     width = w;
@@ -9,7 +10,7 @@ function getBoundingBox(element, {w, h} = {}) {
     bottom = top + h;
   }
 
-  return {width, height, left, top, right, bottom};
+  return {width, height, left, top, right, bottom, x, y};
 }
 
 function getIntersectionOfRects(rects) {
diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js
index 555d9c8a291..762c18a01f3 100644
--- a/modules/33acrossBidAdapter.js
+++ b/modules/33acrossBidAdapter.js
@@ -14,6 +14,7 @@ import {
 } from '../src/utils.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
+import { percentInView } from '../libraries/percentInView/percentInView.js';
 
 // **************************** UTILS ************************** //
 const BIDDER_CODE = '33across';
@@ -547,7 +548,7 @@ function _isViewabilityMeasurable(element) {
 
 function _getViewability(element, topWin, { w, h } = {}) {
   return topWin.document.visibilityState === 'visible'
-    ? _getPercentInView(element, topWin, { w, h })
+    ? percentInView(element, topWin, { w, h })
     : 0;
 }
 
@@ -582,76 +583,6 @@ function _getMinSize(sizes) {
   return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min);
 }
 
-function _getBoundingBox(element, { w, h } = {}) {
-  let { width, height, left, top, right, bottom } = element.getBoundingClientRect();
-
-  if ((width === 0 || height === 0) && w && h) {
-    width = w;
-    height = h;
-    right = left + w;
-    bottom = top + h;
-  }
-
-  return { width, height, left, top, right, bottom };
-}
-
-function _getIntersectionOfRects(rects) {
-  const bbox = {
-    left: rects[0].left,
-    right: rects[0].right,
-    top: rects[0].top,
-    bottom: rects[0].bottom
-  };
-
-  for (let i = 1; i < rects.length; ++i) {
-    bbox.left = Math.max(bbox.left, rects[i].left);
-    bbox.right = Math.min(bbox.right, rects[i].right);
-
-    if (bbox.left >= bbox.right) {
-      return null;
-    }
-
-    bbox.top = Math.max(bbox.top, rects[i].top);
-    bbox.bottom = Math.min(bbox.bottom, rects[i].bottom);
-
-    if (bbox.top >= bbox.bottom) {
-      return null;
-    }
-  }
-
-  bbox.width = bbox.right - bbox.left;
-  bbox.height = bbox.bottom - bbox.top;
-
-  return bbox;
-}
-
-function _getPercentInView(element, topWin, { w, h } = {}) {
-  const elementBoundingBox = _getBoundingBox(element, { w, h });
-
-  // Obtain the intersection of the element and the viewport
-  const elementInViewBoundingBox = _getIntersectionOfRects([ {
-    left: 0,
-    top: 0,
-    right: topWin.innerWidth,
-    bottom: topWin.innerHeight
-  }, elementBoundingBox ]);
-
-  let elementInViewArea,
-    elementTotalArea;
-
-  if (elementInViewBoundingBox !== null) {
-    // Some or all of the element is in view
-    elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height;
-    elementTotalArea = elementBoundingBox.width * elementBoundingBox.height;
-
-    return ((elementInViewArea / elementTotalArea) * 100);
-  }
-
-  // No overlap between element and the viewport; therefore, the element
-  // lies completely out of view
-  return 0;
-}
-
 /**
  * Viewability contribution to request..
  */
diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js
index d8a77003667..ab048ec1e05 100644
--- a/modules/adagioRtdProvider.js
+++ b/modules/adagioRtdProvider.js
@@ -28,6 +28,7 @@ import {
 } from '../src/utils.js';
 import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js';
 import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 /**
  * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule
@@ -469,8 +470,8 @@ function getElementFromTopWindow(element, currentWindow) {
       return element;
     } else {
       const frame = currentWindow.frameElement;
-      const frameClientRect = frame.getBoundingClientRect();
-      const elementClientRect = element.getBoundingClientRect();
+      const frameClientRect = getBoundingClientRect(frame);
+      const elementClientRect = getBoundingClientRect(element);
 
       if (frameClientRect.width !== elementClientRect.width || frameClientRect.height !== elementClientRect.height) {
         return false;
@@ -520,7 +521,7 @@ function getSlotPosition(divId) {
         return '';
       }
 
-      let box = domElement.getBoundingClientRect();
+      let box = getBoundingClientRect(domElement);
 
       const docEl = d.documentElement;
       const body = d.body;
diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js
index ec65ba11205..69b05ccfdba 100644
--- a/modules/concertBidAdapter.js
+++ b/modules/concertBidAdapter.js
@@ -2,6 +2,7 @@ import { logWarn, logMessage, debugTurnedOn, generateUUID, deepAccess } from '..
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { getStorageManager } from '../src/storageManager.js';
 import { hasPurpose1Consent } from '../src/utils/gdpr.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -265,7 +266,7 @@ function getUserId(id, source, uidExt, atype) {
 
 function getOffset(el) {
   if (el) {
-    const rect = el.getBoundingClientRect();
+    const rect = getBoundingClientRect(el);
     return {
       left: rect.left + window.scrollX,
       top: rect.top + window.scrollY
diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js
index 5bd1eab8863..135e3c8a86d 100644
--- a/modules/eplanningBidAdapter.js
+++ b/modules/eplanningBidAdapter.js
@@ -5,6 +5,7 @@ import {getStorageManager} from '../src/storageManager.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
 import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 const BIDDER_CODE = 'eplanning';
 export const storage = getStorageManager({bidderCode: BIDDER_CODE});
@@ -364,7 +365,7 @@ function waitForElementsPresent(elements) {
             if (index < 0) {
               elements.forEach(code => {
                 let div = _getAdSlotHTMLElement(code);
-                if (div && div.contains(ad) && div.getBoundingClientRect().width > 0) {
+                if (div && div.contains(ad) && getBoundingClientRect(div).width > 0) {
                   index = elements.indexOf(div.id);
                   adView = div;
                 }
diff --git a/modules/h12mediaBidAdapter.js b/modules/h12mediaBidAdapter.js
index 7b1ba9ee286..845175775d9 100644
--- a/modules/h12mediaBidAdapter.js
+++ b/modules/h12mediaBidAdapter.js
@@ -1,5 +1,6 @@
 import { inIframe, logError, logMessage, deepAccess } from '../src/utils.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 const BIDDER_CODE = 'h12media';
 const DEFAULT_URL = 'https://bidder.h12-media.com/prebid/';
 const DEFAULT_CURRENCY = 'USD';
@@ -35,8 +36,8 @@ export const spec = {
         x: framePos[0],
         y: framePos[1],
       } : {
-        x: adUnitElement && adUnitElement.getBoundingClientRect().x,
-        y: adUnitElement && adUnitElement.getBoundingClientRect().y,
+        x: adUnitElement && getBoundingClientRect(adUnitElement).x,
+        y: adUnitElement && getBoundingClientRect(adUnitElement).y,
       };
 
       const bidrequest = {
@@ -242,8 +243,8 @@ function getFramePos() {
       if (m > 1) {
         t = t.parent
       }
-      frmLeft = frmLeft + t.frameElement.getBoundingClientRect().left;
-      frmTop = frmTop + t.frameElement.getBoundingClientRect().top;
+      frmLeft = frmLeft + getBoundingClientRect(t.frameElement).left;
+      frmTop = frmTop + getBoundingClientRect(t.frameElement).top;
     } catch (o) { /* keep looping */
     }
   } while ((m < 100) && (t.parent !== t.self))
diff --git a/modules/mantisBidAdapter.js b/modules/mantisBidAdapter.js
index 4520bad0f3a..fe81ee3eab2 100644
--- a/modules/mantisBidAdapter.js
+++ b/modules/mantisBidAdapter.js
@@ -1,6 +1,7 @@
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import { getStorageManager } from '../src/storageManager.js';
 import { ajax } from '../src/ajax.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 export const storage = getStorageManager({bidderCode: 'mantis'});
 
@@ -75,7 +76,7 @@ export function onVisible(win, element, doOnVisible, time, pct) {
   interval = setInterval(function () {
     var winHeight = (win.innerHeight || document.documentElement.clientHeight);
     var winWidth = (win.innerWidth || document.documentElement.clientWidth);
-    doCheck(winWidth, winHeight, element.getBoundingClientRect());
+    doCheck(winWidth, winHeight, getBoundingClientRect(element));
   }, 100);
 }
 function storeUuid(uuid) {
diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js
index 1d322a23a77..8b3366d02c2 100644
--- a/modules/marsmediaBidAdapter.js
+++ b/modules/marsmediaBidAdapter.js
@@ -4,6 +4,7 @@ import { deepAccess, getDNT, parseSizesInput, isArray, getWindowTop, deepSetValu
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
 import {config} from '../src/config.js';
+import { percentInView } from '../libraries/percentInView/percentInView.js';
 
 function MarsmediaAdapter() {
   this.code = 'marsmedia';
@@ -369,75 +370,9 @@ function MarsmediaAdapter() {
 
   function _getViewability(element, topWin, { w, h } = {}) {
     return topWin.document.visibilityState === 'visible'
-      ? _getPercentInView(element, topWin, { w, h })
+      ? percentInView(element, topWin, { w, h })
       : 0;
   }
-
-  function _getPercentInView(element, topWin, { w, h } = {}) {
-    const elementBoundingBox = _getBoundingBox(element, { w, h });
-
-    const elementInViewBoundingBox = _getIntersectionOfRects([ {
-      left: 0,
-      top: 0,
-      right: topWin.innerWidth,
-      bottom: topWin.innerHeight
-    }, elementBoundingBox ]);
-
-    let elementInViewArea, elementTotalArea;
-
-    if (elementInViewBoundingBox !== null) {
-      // Some or all of the element is in view
-      elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height;
-      elementTotalArea = elementBoundingBox.width * elementBoundingBox.height;
-
-      return ((elementInViewArea / elementTotalArea) * 100);
-    }
-
-    return 0;
-  }
-
-  function _getBoundingBox(element, { w, h } = {}) {
-    let { width, height, left, top, right, bottom } = element.getBoundingClientRect();
-
-    if ((width === 0 || height === 0) && w && h) {
-      width = w;
-      height = h;
-      right = left + w;
-      bottom = top + h;
-    }
-
-    return { width, height, left, top, right, bottom };
-  }
-
-  function _getIntersectionOfRects(rects) {
-    const bbox = {
-      left: rects[0].left,
-      right: rects[0].right,
-      top: rects[0].top,
-      bottom: rects[0].bottom
-    };
-
-    for (let i = 1; i < rects.length; ++i) {
-      bbox.left = Math.max(bbox.left, rects[i].left);
-      bbox.right = Math.min(bbox.right, rects[i].right);
-
-      if (bbox.left >= bbox.right) {
-        return null;
-      }
-
-      bbox.top = Math.max(bbox.top, rects[i].top);
-      bbox.bottom = Math.min(bbox.bottom, rects[i].bottom);
-
-      if (bbox.top >= bbox.bottom) {
-        return null;
-      }
-    }
-
-    bbox.width = bbox.right - bbox.left;
-    bbox.height = bbox.bottom - bbox.top;
-
-    return bbox;
-  }
 }
 
 export const spec = new MarsmediaAdapter();
diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js
index bd6d684c9a0..dc5ccab4bc2 100644
--- a/modules/medianetBidAdapter.js
+++ b/modules/medianetBidAdapter.js
@@ -22,6 +22,7 @@ import {getGlobal} from '../src/prebidGlobal.js';
 import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
 import {ajax} from '../src/ajax.js';
 import {getViewportCoordinates} from '../libraries/viewport/viewport.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -155,8 +156,8 @@ function getCoordinates(adUnitCode) {
       element = document.getElementById(divId);
     }
   }
-  if (element && element.getBoundingClientRect) {
-    const rect = element.getBoundingClientRect();
+  if (element) {
+    const rect = getBoundingClientRect(element);
     let coordinates = {};
     coordinates.top_left = {
       y: rect.top,
diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js
index 8bf326b4848..ad6d050a035 100644
--- a/modules/onetagBidAdapter.js
+++ b/modules/onetagBidAdapter.js
@@ -7,6 +7,7 @@ import { find } from '../src/polyfill.js';
 import { getStorageManager } from '../src/storageManager.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { deepClone, logError, deepAccess } from '../src/utils.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 /**
  * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest
@@ -307,12 +308,12 @@ function setGeneralInfo(bidRequest) {
 function getSpaceCoords(id) {
   const space = document.getElementById(id);
   try {
-    const { top, left, width, height } = space.getBoundingClientRect();
+    const { top, left, width, height } = getBoundingClientRect(space);
     let window = space.ownerDocument.defaultView;
     const coords = { top: top + window.pageYOffset, left: left + window.pageXOffset, width, height };
     let frame = window.frameElement;
     while (frame != null) {
-      const { top, left } = frame.getBoundingClientRect();
+      const { top, left } = getBoundingClientRect(frame);
       coords.top += top + window.pageYOffset;
       coords.left += left + window.pageXOffset;
       window = window.parent;
diff --git a/modules/onomagicBidAdapter.js b/modules/onomagicBidAdapter.js
index 78f00153a8b..58b97a7f93e 100644
--- a/modules/onomagicBidAdapter.js
+++ b/modules/onomagicBidAdapter.js
@@ -12,6 +12,7 @@ import {
 } from '../src/utils.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER} from '../src/mediaTypes.js';
+import { percentInView } from '../libraries/percentInView/percentInView.js';
 
 const BIDDER_CODE = 'onomagic';
 const URL = 'https://bidder.onomagic.com/hb';
@@ -172,7 +173,7 @@ function _isViewabilityMeasurable(element) {
 
 function _getViewability(element, topWin, { w, h } = {}) {
   return getWindowTop().document.visibilityState === 'visible'
-    ? _getPercentInView(element, topWin, { w, h })
+    ? percentInView(element, topWin, { w, h })
     : 0;
 }
 
@@ -188,75 +189,6 @@ function _getMinSize(sizes) {
   return sizes.reduce((min, size) => size.h * size.w < min.h * min.w ? size : min);
 }
 
-function _getBoundingBox(element, { w, h } = {}) {
-  let { width, height, left, top, right, bottom } = element.getBoundingClientRect();
-
-  if ((width === 0 || height === 0) && w && h) {
-    width = w;
-    height = h;
-    right = left + w;
-    bottom = top + h;
-  }
-
-  return { width, height, left, top, right, bottom };
-}
-
-function _getIntersectionOfRects(rects) {
-  const bbox = {
-    left: rects[0].left,
-    right: rects[0].right,
-    top: rects[0].top,
-    bottom: rects[0].bottom
-  };
-
-  for (let i = 1; i < rects.length; ++i) {
-    bbox.left = Math.max(bbox.left, rects[i].left);
-    bbox.right = Math.min(bbox.right, rects[i].right);
-
-    if (bbox.left >= bbox.right) {
-      return null;
-    }
-
-    bbox.top = Math.max(bbox.top, rects[i].top);
-    bbox.bottom = Math.min(bbox.bottom, rects[i].bottom);
-
-    if (bbox.top >= bbox.bottom) {
-      return null;
-    }
-  }
-
-  bbox.width = bbox.right - bbox.left;
-  bbox.height = bbox.bottom - bbox.top;
-
-  return bbox;
-}
-
-function _getPercentInView(element, topWin, { w, h } = {}) {
-  const elementBoundingBox = _getBoundingBox(element, { w, h });
-
-  // Obtain the intersection of the element and the viewport
-  const elementInViewBoundingBox = _getIntersectionOfRects([ {
-    left: 0,
-    top: 0,
-    right: topWin.innerWidth,
-    bottom: topWin.innerHeight
-  }, elementBoundingBox ]);
-
-  let elementInViewArea, elementTotalArea;
-
-  if (elementInViewBoundingBox !== null) {
-    // Some or all of the element is in view
-    elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height;
-    elementTotalArea = elementBoundingBox.width * elementBoundingBox.height;
-
-    return ((elementInViewArea / elementTotalArea) * 100);
-  }
-
-  // No overlap between element and the viewport; therefore, the element
-  // lies completely out of view
-  return 0;
-}
-
 function _getBidFloor(bid) {
   if (!isFn(bid.getFloor)) {
     return bid.params.bidFloor ? bid.params.bidFloor : null;
diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js
index ad579f1cdbf..a8b8fd8fec1 100644
--- a/modules/seedtagBidAdapter.js
+++ b/modules/seedtagBidAdapter.js
@@ -1,3 +1,4 @@
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 import { registerBidder } from '../src/adapters/bidderFactory.js';
 import { config } from '../src/config.js';
 import { BANNER, VIDEO } from '../src/mediaTypes.js';
@@ -220,7 +221,7 @@ function geom(adunitCode) {
   const slot = document.getElementById(adunitCode);
   if (slot) {
     const scrollY = window.scrollY;
-    const { top, left, width, height } = slot.getBoundingClientRect();
+    const { top, left, width, height } = getBoundingClientRect(slot);
     const viewport = {
       width: window.innerWidth,
       height: window.innerHeight,
diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js
index 5a492a8e5e0..2acdaf5f66f 100644
--- a/modules/stroeerCoreBidAdapter.js
+++ b/modules/stroeerCoreBidAdapter.js
@@ -2,6 +2,7 @@ import { buildUrl, deepAccess, deepSetValue, generateUUID, getWindowSelf, getWin
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {BANNER, VIDEO} from '../src/mediaTypes.js';
 import {find} from '../src/polyfill.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 const GVL_ID = 136;
 const BIDDER_CODE = 'stroeerCore';
@@ -163,7 +164,7 @@ const elementInView = (elementId) => {
   };
 
   const visibleInWindow = (el, win) => {
-    const rect = el.getBoundingClientRect();
+    const rect = getBoundingClientRect(el);
     const inView = (rect.top + rect.height >= 0) && (rect.top <= win.innerHeight);
 
     if (win !== win.parent) {
diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js
index 54b74c7ccd4..ca98e8e14fc 100644
--- a/modules/underdogmediaBidAdapter.js
+++ b/modules/underdogmediaBidAdapter.js
@@ -12,6 +12,7 @@ import {
 import {config} from '../src/config.js';
 import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
+import { percentInView } from '../libraries/percentInView/percentInView.js';
 
 const BIDDER_CODE = 'underdogmedia';
 const UDM_ADAPTER_VERSION = '7.30V';
@@ -262,106 +263,13 @@ function _getViewability(element, topWin, {
   h
 } = {}) {
   return topWin.document.visibilityState === 'visible'
-    ? _getPercentInView(element, topWin, {
+    ? percentInView(element, topWin, {
       w,
       h
     })
     : 0
 }
 
-function _getPercentInView(element, topWin, {
-  w,
-  h
-} = {}) {
-  const elementBoundingBox = _getBoundingBox(element, {
-    w,
-    h
-  });
-
-  // Obtain the intersection of the element and the viewport
-  const elementInViewBoundingBox = _getIntersectionOfRects([{
-    left: 0,
-    top: 0,
-    right: topWin.innerWidth,
-    bottom: topWin.innerHeight
-  }, elementBoundingBox]);
-
-  let elementInViewArea,
-    elementTotalArea;
-
-  if (elementInViewBoundingBox !== null) {
-    // Some or all of the element is in view
-    elementInViewArea = elementInViewBoundingBox.width * elementInViewBoundingBox.height;
-    elementTotalArea = elementBoundingBox.width * elementBoundingBox.height;
-
-    return ((elementInViewArea / elementTotalArea) * 100);
-  }
-
-  // No overlap between element and the viewport; therefore, the element
-  // lies completely out of view
-  return 0;
-}
-
-function _getBoundingBox(element, {
-  w,
-  h
-} = {}) {
-  let {
-    width,
-    height,
-    left,
-    top,
-    right,
-    bottom
-  } = element.getBoundingClientRect();
-
-  if ((width === 0 || height === 0) && w && h) {
-    width = w;
-    height = h;
-    right = left + w;
-    bottom = top + h;
-  }
-
-  return {
-    width,
-    height,
-    left,
-    top,
-    right,
-    bottom
-  };
-}
-
-function _getIntersectionOfRects(rects) {
-  const bbox = {
-    left: rects[0].left,
-    right: rects[0].right,
-    top: rects[0].top,
-    bottom: rects[0].bottom
-  };
-
-  for (let i = 1; i < rects.length; ++i) {
-    bbox.left = Math.max(bbox.left, rects[i].left);
-    bbox.right = Math.min(bbox.right, rects[i].right);
-
-    if (bbox.left >= bbox.right) {
-      return null;
-    }
-
-    bbox.top = Math.max(bbox.top, rects[i].top);
-    bbox.bottom = Math.min(bbox.bottom, rects[i].bottom);
-
-    if (bbox.top >= bbox.bottom) {
-      return null;
-    }
-  }
-
-  bbox.width = bbox.right - bbox.left;
-  bbox.height = bbox.bottom - bbox.top;
-
-  return bbox;
-}
-
 function makeNotification(bid, mid, bidParam) {
   let url = mid.notification_url;
 
diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js
index 6fa14f5d68c..aa939d4584a 100644
--- a/modules/widespaceBidAdapter.js
+++ b/modules/widespaceBidAdapter.js
@@ -3,6 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js';
 import {deepClone, parseQueryStringParameters, parseSizesInput} from '../src/utils.js';
 import {find, includes} from '../src/polyfill.js';
 import {getStorageManager} from '../src/storageManager.js';
+import { getBoundingClientRect } from '../libraries/boundingClientRect/boundingClientRect.js';
 
 const BIDDER_CODE = 'widespace';
 const WS_ADAPTER_VERSION = '2.0.1';
@@ -194,8 +195,8 @@ function pixelSyncPossibility() {
 }
 
 function visibleOnLoad(element) {
-  if (element && element.getBoundingClientRect) {
-    const topPos = element.getBoundingClientRect().top;
+  if (element) {
+    const topPos = getBoundingClientRect(element).top;
     return topPos < screen.height && topPos >= window.top.pageYOffset ? 1 : 0;
   }
   return '';
diff --git a/test/spec/libraries/boundingClientRect_spec.js b/test/spec/libraries/boundingClientRect_spec.js
new file mode 100644
index 00000000000..e990889357d
--- /dev/null
+++ b/test/spec/libraries/boundingClientRect_spec.js
@@ -0,0 +1,67 @@
+import { getBoundingClientRect } from '../../../libraries/boundingClientRect/boundingClientRect';
+import { startAuction } from '../../../src/prebid';
+
+describe('getBoundingClientRect', () => {
+  let element, getBoundingClientRectSpy;
+
+  beforeEach(() => {
+    element = document.createElement('div');
+    getBoundingClientRectSpy = sinon.spy(element, 'getBoundingClientRect');
+  })
+
+  afterEach(() => {
+    getBoundingClientRectSpy.restore();
+  });
+
+  it('should fire getBoundingClientRect on element initially', () => {
+    getBoundingClientRect(element);
+    expect(getBoundingClientRectSpy.calledOnce).to.equal(true);
+  });
+
+  it('should not fire getBoundingClientRect twice for the same element', () => {
+    getBoundingClientRect(element);
+    getBoundingClientRect(element);
+    expect(getBoundingClientRectSpy.callCount).to.equal(1);
+  });
+
+  it('should fire getBoundingClientRect on element again if new auction was started', (done) => {
+    const onAuctionDone = function() {
+      getBoundingClientRect(element);
+      expect(getBoundingClientRectSpy.callCount).to.equal(2);
+      done();
+    }
+
+    const mockAuctionData = {
+      bidsBackHandler: onAuctionDone,
+      ortb2Fragments: {},
+      timeout: 3000,
+      adUnitCodes: [],
+      adUnits: [],
+      ttlBuffer: 1000,
+      auctionId: '909090',
+      defer: {resolve: () => {}}
+    };
+
+    getBoundingClientRect(element);
+    startAuction(mockAuctionData);
+  });
+
+  it('should return same value for multiple calls', () => {
+    const expectedWidth = 200
+    const expectedHeight = 300;
+    element.style.width = expectedWidth + 'px';
+    element.style.height = expectedHeight + 'px';
+    document.body.append(element);
+
+    const result1 = getBoundingClientRect(element);
+
+    expect(result1.width).to.deep.eql(expectedWidth);
+    expect(result1.height).to.deep.eql(expectedHeight);
+
+    const result2 = getBoundingClientRect(element);
+
+    expect(result2).to.deep.eql(result1);
+
+    document.body.removeChild(element);
+  });
+});
diff --git a/test/spec/modules/h12mediaBidAdapter_spec.js b/test/spec/modules/h12mediaBidAdapter_spec.js
index 9861069f260..154da869b38 100644
--- a/test/spec/modules/h12mediaBidAdapter_spec.js
+++ b/test/spec/modules/h12mediaBidAdapter_spec.js
@@ -1,7 +1,7 @@
 import {expect} from 'chai';
 import {spec} from 'modules/h12mediaBidAdapter';
 import {newBidder} from 'src/adapters/bidderFactory';
-import * as utils from 'src/utils';
+import {clearCache} from '../../../libraries/boundingClientRect/boundingClientRect';
 
 describe('H12 Media Adapter', function () {
   const DEFAULT_CURRENCY = 'USD';
@@ -158,6 +158,7 @@ describe('H12 Media Adapter', function () {
       left: 10,
       top: 10,
     });
+    clearCache();
   });
 
   afterEach(function () {


Reviews:
user: login: dgirardi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T15:55:43Z
user: login: patmmccann
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T18:56:55Z
user: login: dgirardi
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-11T19:32:54Z
user: login: patmmccann
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-13T16:56:25Z

Review comments:
diff_hunk: @@ -1,6 +1,26 @@
+import { startAuction } from '../../src/prebid.js';
 
-function getBoundingBox(element, {w, h} = {}) {
-  let {width, height, left, top, right, bottom} = element.getBoundingClientRect();
+const cache = new Map();
+
+startAuction.before((next, auctionConfig) => {
+  clearCache();
+  next(auctionConfig);
+});
+
+export function clearCache() {
+  cache.clear();
+}
+
+export function getBoundingBox(element, {w, h} = {}) {
path: libraries/percentInView/percentInView.js
user: login: dgirardi
body: most of the consumers of this are only interested in the bounding rect and do not need the `w/h` adjustment or the rest of this library. IMO it makes sense to extract it into a different library.
created_at: 2025-03-11T15:55:04Z
author_association: COLLABORATOR
diff_hunk: @@ -1,6 +1,26 @@
+import { startAuction } from '../../src/prebid.js';
 
-function getBoundingBox(element, {w, h} = {}) {
-  let {width, height, left, top, right, bottom} = element.getBoundingClientRect();
+const cache = new Map();
+
+startAuction.before((next, auctionConfig) => {
+  clearCache();
+  next(auctionConfig);
+});
+
+export function clearCache() {
+  cache.clear();
+}
+
+export function getBoundingBox(element, {w, h} = {}) {
path: libraries/percentInView/percentInView.js
user: login: patmmccann
body: why a different library instead of just a different function to import?
created_at: 2025-03-11T18:56:55Z
author_association: COLLABORATOR
diff_hunk: @@ -1,6 +1,26 @@
+import { startAuction } from '../../src/prebid.js';
 
-function getBoundingBox(element, {w, h} = {}) {
-  let {width, height, left, top, right, bottom} = element.getBoundingClientRect();
+const cache = new Map();
+
+startAuction.before((next, auctionConfig) => {
+  clearCache();
+  next(auctionConfig);
+});
+
+export function clearCache() {
+  cache.clear();
+}
+
+export function getBoundingBox(element, {w, h} = {}) {
path: libraries/percentInView/percentInView.js
user: login: dgirardi
body: because importing a function from a library imports the whole library.
created_at: 2025-03-11T19:32:54Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-07T13:23:49Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/8f7ba04a75b52e182453836d964454faca1426f8/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-07T13:24:06Z
body: Tread carefully! This PR adds **3** linter errors (possibly disabled through directives):

 * `libraries/percentInView/percentInView.js` (+3 errors)

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-07T15:10:20Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c39293dfddc34766548e406ee8cce2197e7cd3be/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-07T15:10:30Z
body: Tread carefully! This PR adds **1** linter error (possibly disabled through directives):

 * `libraries/percentInView/percentInView.js` (+1 error)

author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-07T15:13:05Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/796bc2495291a9cd4f9ee8e0a69ed26d189344b2/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-11T09:01:28Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d987a44b88a94845453a6034dff4e0c4e3182829/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-11T12:06:44Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/4777db4d495b53e72eae7208c5c0f6bed9716aaf/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T17:55:15Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/d6889c4484a6ddccd5ae2aaa01f4a06cff4b70c4/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-12T17:57:51Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/c4cec130d35cff7b1143edf31f33cf052cd53eba/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-13T12:12:05Z
body: Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:

- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/omsBidAdapter.js#L43-L50) has 10 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/onomagicBidAdapter.js#L39-L49)
- [`modules/omsBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/omsBidAdapter.js#L284-L310) has 29 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/onomagicBidAdapter.js#L179-L207)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/marsmediaBidAdapter.js#L44-L57) has 16 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/rhythmoneBidAdapter.js#L43-L56)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/marsmediaBidAdapter.js#L89-L161) has 75 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/rhythmoneBidAdapter.js#L70-L142)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/marsmediaBidAdapter.js#L163-L174) has 14 duplicated lines with [`modules/onomagicBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/onomagicBidAdapter.js#L38-L49)
- [`modules/marsmediaBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/marsmediaBidAdapter.js#L274-L297) has 26 duplicated lines with [`modules/rhythmoneBidAdapter.js`](https://github.com/prebid/Prebid.js/blob/dcb1dd0dfa3134bae9cfd170f88193c24d43932c/modules/rhythmoneBidAdapter.js#L218-L241)

Reducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀
author_association: NONE