Skip to content

Update display controls layers in the WebGUI#10647

Open
jorge-ferreira-pii wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:feature-display-controls-layers-WebGUI
Open

Update display controls layers in the WebGUI#10647
jorge-ferreira-pii wants to merge 2 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:feature-display-controls-layers-WebGUI

Conversation

@jorge-ferreira-pii

Copy link
Copy Markdown
Contributor

Addresses a portion of #10619

Three blocks of functionality, all in OpenROAD's src/web module, closing gaps from table 2.2 ("Display controls — layers") and related areas:

Fill pattern per layer + Implant/Other categorization (❌/🟡 gaps in table 2.2)
"Layer Config" dialog (color + pattern + opacity) — parity with the GUI's DisplayColorDialog
Geometry rasterization for implant/other + fills (dbFill) — ensuring these layers do not render empty

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
@jorge-ferreira-pii jorge-ferreira-pii self-assigned this Jun 12, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'Layer Config' modal dialog to the web viewer, enabling users to customize layer colors, fill patterns, and opacity, bringing it to structural parity with the Qt GUI. It also adds support for rendering metal fills and groups layers by type (Backside, Implant, Other) in the layer tree. The review feedback highlights several key improvement opportunities: enhancing robustness when parsing cookies by validating the JSON structure, mitigating a potential XSS vulnerability in the modal dialog by avoiding direct HTML interpolation of layer names, and optimizing rendering performance in TileGenerator by unswitching loops to avoid redundant, loop-invariant pattern checks.

Comment thread src/web/src/display-controls.js Outdated
Comment on lines +121 to +132
try {
const raw = getCookie('or_layer_patterns');
if (raw) savedLayerPatterns = JSON.parse(decodeURIComponent(raw));
} catch (_) { /* ignore */ }
try {
const raw = getCookie('or_layer_colors');
if (raw) savedLayerColors = JSON.parse(decodeURIComponent(raw));
} catch (_) { /* ignore */ }
try {
const raw = getCookie('or_layer_opacity');
if (raw) savedLayerOpacity = JSON.parse(decodeURIComponent(raw));
} catch (_) { /* ignore */ }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Parsing cookies directly with JSON.parse without validating the type of the returned value can lead to runtime TypeErrors if the cookie is corrupted or set to a non-object value (e.g., "null" or a number). It is safer to use a helper function that validates that the parsed result is a non-null object.

Suggested change
try {
const raw = getCookie('or_layer_patterns');
if (raw) savedLayerPatterns = JSON.parse(decodeURIComponent(raw));
} catch (_) { /* ignore */ }
try {
const raw = getCookie('or_layer_colors');
if (raw) savedLayerColors = JSON.parse(decodeURIComponent(raw));
} catch (_) { /* ignore */ }
try {
const raw = getCookie('or_layer_opacity');
if (raw) savedLayerOpacity = JSON.parse(decodeURIComponent(raw));
} catch (_) { /* ignore */ }
const parseCookieObject = (name) => {
try {
const raw = getCookie(name);
if (raw) {
const parsed = JSON.parse(decodeURIComponent(raw));
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
return parsed;
}
}
} catch (_) {}
return {};
};
savedLayerPatterns = parseCookieObject('or_layer_patterns');
savedLayerColors = parseCookieObject('or_layer_colors');
savedLayerOpacity = parseCookieObject('or_layer_opacity');

Comment on lines +547 to +568
const { dialog, close } = createModalDialog(`
<div class="modal-dialog layer-config-dialog">
<h3>Layer Config — ${node.data.name}</h3>
<div class="layer-config-row">
<label for="lc-color">Color</label>
<input type="color" id="lc-color" value="${rgbToHex(curColor)}">
</div>
<div class="layer-config-row">
<label for="lc-pattern">Fill pattern</label>
<select id="lc-pattern">${patternOptions}</select>
</div>
<div class="layer-config-row">
<label for="lc-opacity">Opacity</label>
<input type="range" id="lc-opacity" min="0" max="100"
value="${Math.round(curOpacity * 100)}">
<span id="lc-opacity-val">${Math.round(curOpacity * 100)}%</span>
</div>
<div class="modal-buttons">
<button id="lc-cancel">Cancel</button>
<button id="lc-ok" class="primary">OK</button>
</div>
</div>`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

Interpolating node.data.name directly into the HTML string passed to createModalDialog can lead to Cross-Site Scripting (XSS) vulnerabilities if a design file contains a maliciously crafted layer name. It is safer to use textContent to set the title dynamically.

        const { dialog, close } = createModalDialog(
            '<div class="modal-dialog layer-config-dialog">' +
            '<h3 id="lc-title"></h3>' +
            '<div class="layer-config-row">' +
            '    <label for="lc-color">Color</label>' +
            '    <input type="color" id="lc-color" value="' + rgbToHex(curColor) + '">' +
            '</div>' +
            '<div class="layer-config-row">' +
            '    <label for="lc-pattern">Fill pattern</label>' +
            '    <select id="lc-pattern">' + patternOptions + '</select>' +
            '</div>' +
            '<div class="layer-config-row">' +
            '    <label for="lc-opacity">Opacity</label>' +
            '    <input type="range" id="lc-opacity" min="0" max="100" value="' + Math.round(curOpacity * 100) + '">' +
            '    <span id="lc-opacity-val">' + Math.round(curOpacity * 100) + '%</span>' +
            '</div>' +
            '<div class="modal-buttons">' +
            '    <button id="lc-cancel">Cancel</button>' +
            '    <button id="lc-ok" class="primary">OK</button>' +
            '</div>' +
            '</div>'
        );

        dialog.querySelector('#lc-title').textContent = 'Layer Config — ' + node.data.name;

Comment thread src/web/src/tile_generator.cpp Outdated
Comment on lines 3777 to 3787
for (int iy = rect.yMin(); iy < rect.yMax(); ++iy) {
const int draw_y = (255 - iy);
const int gy = px_origin_y + draw_y;
for (int ix = rect.xMin(); ix < rect.xMax(); ++ix) {
const int draw_y = (255 - iy);
if (pattern != FillPattern::kSolid
&& !patternMask(pattern, px_origin_x + ix, gy)) {
continue;
}
setPixel(buffer, ix, draw_y, color);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Checking pattern != FillPattern::kSolid inside the inner loop is inefficient because pattern is loop-invariant. Since kSolid is the default and most common case, unswitching the loop to separate the solid fill from the patterned fill will improve rendering performance.

Suggested change
for (int iy = rect.yMin(); iy < rect.yMax(); ++iy) {
const int draw_y = (255 - iy);
const int gy = px_origin_y + draw_y;
for (int ix = rect.xMin(); ix < rect.xMax(); ++ix) {
const int draw_y = (255 - iy);
if (pattern != FillPattern::kSolid
&& !patternMask(pattern, px_origin_x + ix, gy)) {
continue;
}
setPixel(buffer, ix, draw_y, color);
}
}
if (pattern == FillPattern::kSolid) {
for (int iy = rect.yMin(); iy < rect.yMax(); ++iy) {
const int draw_y = (255 - iy);
for (int ix = rect.xMin(); ix < rect.xMax(); ++ix) {
setPixel(buffer, ix, draw_y, color);
}
}
} else {
for (int iy = rect.yMin(); iy < rect.yMax(); ++iy) {
const int draw_y = (255 - iy);
const int gy = px_origin_y + draw_y;
for (int ix = rect.xMin(); ix < rect.xMax(); ++ix) {
if (!patternMask(pattern, px_origin_x + ix, gy)) {
continue;
}
setPixel(buffer, ix, draw_y, color);
}
}
}

Comment thread src/web/src/tile_generator.cpp Outdated
Comment on lines +887 to +890
if (pattern != FillPattern::kSolid
&& !patternMask(pattern, px_origin_x + ix, gy)) {
continue;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Checking pattern != FillPattern::kSolid inside the inner loop is inefficient because pattern is loop-invariant. Unswitching the loop for the solid fill case (which is the default and most common case) will avoid redundant checks and allow the compiler to better optimize the loop.

Example optimization:

      if (pattern == FillPattern::kSolid) {
        for (int ix = ix_min; ix < ix_max; ++ix) {
          if (blend) {
            blendPixel(image, ix, draw_y, color);
          } else {
            setPixel(image, ix, draw_y, color);
          }
        }
      } else {
        for (int ix = ix_min; ix < ix_max; ++ix) {
          if (patternMask(pattern, px_origin_x + ix, gy)) {
            if (blend) {
              blendPixel(image, ix, draw_y, color);
            } else {
              setPixel(image, ix, draw_y, color);
            }
          }
        }
      }

… fill loops

- display-controls.js: parse the per-layer cookies (or_layer_patterns/
  colors/opacity) through a parseCookieObject() helper that rejects
  non-object JSON, avoiding a TypeError on a corrupted cookie.
- display-controls.js: set the Layer Config dialog title via textContent
  instead of HTML interpolation to avoid XSS from a layer name.
- tile_generator.cpp: unswitch the pattern check out of the per-pixel
  inner loop in drawFilledRect() and fillPolygon() so the common solid
  fill skips the loop-invariant patternMask test.

Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
@jorge-ferreira-pii jorge-ferreira-pii marked this pull request as ready for review June 12, 2026 19:59
@jorge-ferreira-pii jorge-ferreira-pii requested a review from a team as a code owner June 12, 2026 19:59
@maliberty

Copy link
Copy Markdown
Member

fails for me, in console
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants