Page profiler never stores above-the-fold background images: `b` is always sent with empty selector maps
メンテナーはふだん 1 日以内に返信
@girishpanchal30 がすでに取り組んでいます。
2026年11月6日 から。
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 77/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- javascript
- 領域
- performance
調査の方向性
assets/js/modules/main.jsの110~139行付近を、assets/js/modules/image-detector.jsの82~104行付近にあるobserver callbackとあわせて読みます。背景のlazy loadingを有効にし、?optml_debug=1を指定して再現した後、背景セレクターのpayloadを調べます。各デバイスについて、above-the-foldの背景要素とそのURLが保存され、それに対応するlazy-load除外が設定されれば完了です。
索引モデルが issue の本文から書いたものです。
説明
Description
On a normal page visit, the page profiler sends every background selector with an empty object, for example "b":{"[style*=\"background-image:url(\"]":{}}. This happens even when a background element is above the fold and its URL has been found. The stored profile ends up with "bg":{"<selector>":[]} for every device. Optimole therefore never learns which background images are above the fold, and the lazy-load CSS can only exempt the LCP background.
Cause
In assets/js/modules/main.js, the IntersectionObserver is created with the first selectorMap instance. Shortly after, selectorMap is replaced with a new Map:
// main.js:110-111
let selectorMap = new Map();
const observer = optmlImageDetector.createIntersectionObserver(aboveTheFoldImages, selectorMap);
...
// main.js:135
selectorMap = backgroundResult.selectorMap;
The observer callback (image-detector.js:100) checks selectorMap.has(baseSelector) against the original Map, which is always empty. Above-the-fold background elements are therefore never recorded. Meanwhile _processBackgroundSelectors (main.js:174) builds b from the new Map, which has the selector keys but no entries. With ?optml_debug=1, the log never prints Element with selector "…" is above the fold for background elements.
Expected: each matched selector lists the above-the-fold elements and their URLs, e.g. {"[style*=\"background-image:url(\"]":{"#content > … > div.wp-block-cover__image-background…":["https://…/qa-hero-bg.jpg"]}}.
Verification of the cause
I changed one line in the served script so it fills the existing Map instead of replacing it (backgroundResult.selectorMap.forEach((v, k) => selectorMap.set(k, v))). With that change:
- The payload contained the selector and its URL.
- The server stored them for mobile and desktop.
- The lazy-load CSS on the page added a
:not(...)exclusion for each above-the-fold background element, not only the LCP one.
I tested this on a page with two background groups side by side.
Impact
On pages with a single hero, nothing visible changes, because the hero is also the LCP element and is exempted and preloaded anyway. On pages with several background images above the fold, the ones that aren't the LCP element are still lazy-loaded. They can appear late and cost LCP/CLS points, which is what the profiler is meant to prevent.
Possible fix
Fill the existing Map, or pass the Map from processBackgroundSelectors to the observer, instead of reassigning selectorMap.
A related timing detail: extractBackgroundImageUrls runs right after processBackgroundSelectors (main.js:139), before the 300/600 ms wait. A background that gets optml-bg-lazyloaded during the wait would be sent with null URLs.
Not related to #1204 / #1214: it appears on both the released 4.2.17 build and the #1214 build. Found while testing #1204.
Step-by-step reproduction instructions
- Optimole 4.2.17, connected, with Lazy load and background lazy load on.
- Create a page whose first block has a background image in an inline style. For example, a Cover block with "Fixed background" on, or a Group block with a background image.
- In a private window (logged out), open the page with
?optml_debug=1appended. Keep the console open. - In the console, look at
Sending background selectors:. Or look at the payload of the request to/wp-json/optml/v1/optimizations. - Load the page in a mobile-width window too, so both devices are profiled. Then open it with
?optml_debug=trueand read the<!-- plugin=optimole-wp measurement#… -->comment after</html>.
Result: b contains each matched selector with {}, and the stored profile shows "bg":{"…":[]} for both devices. Only the LCP background (lcp.bgUrls) is stored.
Screenshots, screen recording, code snippet or Help Scout ticket
Code links:
https://github.com/Codeinwp/optimole-wp/blob/v4.2.17/assets/js/modules/main.js#L110-L135
https://github.com/Codeinwp/optimole-wp/blob/v4.2.17/assets/js/modules/image-detector.js#L82-L104
Environment info
Optimole 4.2.17 (same code on master), WordPress + Neve, PHP 8.5, Chromium desktop 1440px / mobile 390px
Is the issue you are reporting a regression
No
- 主要言語
- PHP
- スター
- 74
- フォーク
- 14
- 平均マージ
- 1日 20時間
- マージ済み PR(30日)
- 39
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
Codeinwp/optimole-wp のほかの issue
-
customer report feature-request-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
Codeinwp/optimole-wp#1239 ·
メンテナーはふだん 1 日以内に返信
-
customer report feature-request-triage
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
Codeinwp/optimole-wp#1231 ·
メンテナーはふだん 1 日以内に返信
-
bug-report customer report
難易度 4/5 3〜5日 初心者へのやさしさ 56/100
Codeinwp/optimole-wp#1211 ·
メンテナーはふだん 1 日以内に返信
-
customer report feature-request-triage
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
Codeinwp/optimole-wp#1179 ·
メンテナーはふだん 1 日以内に返信
-
customer report feature-request-triage
難易度 4/5 3〜5日 初心者へのやさしさ 54/100
Codeinwp/optimole-wp#1178 ·
メンテナーはふだん 1 日以内に返信
Codeinwp/optimole-wp の issue をすべて見る
似ている issue
-
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信
-
claude
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
sprain/sprainocchio#38 ·
メンテナーはふだん 1 日以内に返信
-
sync-en
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
メンテナーはふだん 1 日以内に返信
-
sync-en
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
Перевод устарел
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信