Skip to content

Commit b652baf

Browse files
authored
Merge pull request #10277 from gadfort/web-title
web: mirror GUI title in browser tab
2 parents 20e96ff + 0c45fbd commit b652baf

8 files changed

Lines changed: 107 additions & 0 deletions

File tree

src/web/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ genrule(
4040
"src/tcl-completer.js",
4141
"src/hierarchy-browser.js",
4242
"src/menu-bar.js",
43+
"src/title.js",
4344
"src/clock-tree-widget.js",
4445
"src/schematic-widget.js",
4546
"src/charts-widget.js",
@@ -64,6 +65,7 @@ genrule(
6465
" $(location src/tcl-completer.js)" +
6566
" $(location src/hierarchy-browser.js)" +
6667
" $(location src/menu-bar.js)" +
68+
" $(location src/title.js)" +
6769
" $(location src/clock-tree-widget.js)" +
6870
" $(location src/schematic-widget.js)" +
6971
" $(location src/charts-widget.js)" +
@@ -89,6 +91,7 @@ _WEB_ASSET_FILES = [
8991
"src/tcl-completer.js",
9092
"src/hierarchy-browser.js",
9193
"src/menu-bar.js",
94+
"src/title.js",
9295
"src/clock-tree-widget.js",
9396
"src/schematic-widget.js",
9497
"src/charts-widget.js",

src/web/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ set(WEB_ASSET_FILES
2828
src/tcl-completer.js
2929
src/hierarchy-browser.js
3030
src/menu-bar.js
31+
src/title.js
3132
src/clock-tree-widget.js
3233
src/schematic-widget.js
3334
src/charts-widget.js
@@ -61,6 +62,7 @@ add_custom_command(
6162
${CMAKE_CURRENT_SOURCE_DIR}/src/tcl-completer.js
6263
${CMAKE_CURRENT_SOURCE_DIR}/src/hierarchy-browser.js
6364
${CMAKE_CURRENT_SOURCE_DIR}/src/menu-bar.js
65+
${CMAKE_CURRENT_SOURCE_DIR}/src/title.js
6466
${CMAKE_CURRENT_SOURCE_DIR}/src/clock-tree-widget.js
6567
${CMAKE_CURRENT_SOURCE_DIR}/src/schematic-widget.js
6668
${CMAKE_CURRENT_SOURCE_DIR}/src/charts-widget.js

src/web/src/main.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { SchematicWidget } from './schematic-widget.js';
1717
import { DrcWidget } from './drc-widget.js';
1818
import { TclCompleter } from './tcl-completer.js';
1919
import { setCookie } from './theme.js';
20+
import { updateDocumentTitle } from './title.js';
2021

2122
// ─── Status Indicator ───────────────────────────────────────────────────────
2223

@@ -744,6 +745,7 @@ app.websocketManager.readyPromise.then(async () => {
744745
]);
745746
app.hasLiberty = techData.has_liberty;
746747
app.techData = techData;
748+
updateDocumentTitle(techData.block_name);
747749

748750
// --- Set Bounds ---
749751
const designBounds = boundsData.bounds;

src/web/src/tile_generator.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2717,6 +2717,9 @@ void serializeTechResponse(JsonBuilder& b, const TileGenerator& gen)
27172717
b.field("has_liberty", gen.hasSta());
27182718
if (gen.getBlock()) {
27192719
b.field("dbu_per_micron", gen.getBlock()->getDbUnitsPerMicron());
2720+
b.field("block_name", gen.getBlock()->getName());
2721+
} else {
2722+
b.field("block_name", std::string());
27202723
}
27212724
b.endObject();
27222725
}

src/web/src/title.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// Copyright (c) 2026, The OpenROAD Authors
3+
4+
// Mirrors the GUI's window title (mainWindow.cpp::updateTitle):
5+
// "OpenROAD - <block name>" when a design is loaded
6+
// "OpenROAD" when no design is loaded
7+
export function updateDocumentTitle(blockName, doc = (typeof document !== 'undefined' ? document : null)) {
8+
const base = 'OpenROAD';
9+
const title = blockName ? `${base} - ${blockName}` : base;
10+
if (doc) doc.title = title;
11+
return title;
12+
}

src/web/test/BUILD

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ js_test(
149149
no_copy_to_bin = JS_FILES,
150150
)
151151

152+
js_test(
153+
name = "title_test",
154+
data = JS_FILES,
155+
entry_point = "js/test-title.js",
156+
no_copy_to_bin = JS_FILES,
157+
)
158+
152159
cc_test(
153160
name = "clock_tree_report_test",
154161
srcs = ["cpp/TestClockTreeReport.cpp"],

src/web/test/cpp/TestTileGenerator.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <vector>
1010

1111
#include "gtest/gtest.h"
12+
#include "json_builder.h"
1213
#include "odb/db.h"
1314
#include "odb/dbTypes.h"
1415
#include "odb/geom.h"
@@ -574,5 +575,25 @@ TEST_F(RowRenderingTest, RowsDefaultOff)
574575
EXPECT_FALSE(vis.rows);
575576
}
576577

578+
//------------------------------------------------------------------------------
579+
// serializeTechResponse — exercises the contract main.js relies on for the
580+
// document title (techData.block_name).
581+
//------------------------------------------------------------------------------
582+
583+
TEST_F(TileGeneratorTest, SerializeTechResponseContainsBlockName)
584+
{
585+
// Nangate45Fixture creates the block with name "top".
586+
makeTileGen();
587+
JsonBuilder b;
588+
serializeTechResponse(b, *tile_gen_);
589+
const std::string json = b.str();
590+
// Field name and value should both appear. Looser than a full JSON
591+
// parse but sufficient: this is the contract main.js consumes.
592+
EXPECT_NE(json.find("\"block_name\""), std::string::npos)
593+
<< "tech response missing block_name key; got: " << json;
594+
EXPECT_NE(json.find("\"top\""), std::string::npos)
595+
<< "tech response missing block name value \"top\"; got: " << json;
596+
}
597+
577598
} // namespace
578599
} // namespace web

src/web/test/js/test-title.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// Copyright (c) 2026, The OpenROAD Authors
3+
4+
import { describe, it } from 'node:test';
5+
import assert from 'node:assert/strict';
6+
import { updateDocumentTitle } from '../../src/title.js';
7+
8+
describe('updateDocumentTitle', () => {
9+
it('uses bare "OpenROAD" when no block name given', () => {
10+
const doc = { title: '' };
11+
const out = updateDocumentTitle('', doc);
12+
assert.equal(out, 'OpenROAD');
13+
assert.equal(doc.title, 'OpenROAD');
14+
});
15+
16+
it('uses bare "OpenROAD" when block name is null', () => {
17+
const doc = { title: '' };
18+
const out = updateDocumentTitle(null, doc);
19+
assert.equal(out, 'OpenROAD');
20+
assert.equal(doc.title, 'OpenROAD');
21+
});
22+
23+
it('uses bare "OpenROAD" when block name is undefined', () => {
24+
const doc = { title: '' };
25+
const out = updateDocumentTitle(undefined, doc);
26+
assert.equal(out, 'OpenROAD');
27+
assert.equal(doc.title, 'OpenROAD');
28+
});
29+
30+
it('mirrors GUI format with " - " separator when block name present', () => {
31+
// GUI uses "{window_title} - {block_name}" (mainWindow.cpp:540).
32+
const doc = { title: '' };
33+
const out = updateDocumentTitle('gcd', doc);
34+
assert.equal(out, 'OpenROAD - gcd');
35+
assert.equal(doc.title, 'OpenROAD - gcd');
36+
});
37+
38+
it('handles block names with special characters verbatim', () => {
39+
const doc = { title: '' };
40+
const out = updateDocumentTitle('top_module/foo', doc);
41+
assert.equal(out, 'OpenROAD - top_module/foo');
42+
});
43+
44+
it('overwrites a previously set title', () => {
45+
const doc = { title: 'old' };
46+
updateDocumentTitle('design1', doc);
47+
assert.equal(doc.title, 'OpenROAD - design1');
48+
updateDocumentTitle('', doc);
49+
assert.equal(doc.title, 'OpenROAD');
50+
});
51+
52+
it('returns the formatted title without a doc', () => {
53+
// Useful for callers that only need the string.
54+
const out = updateDocumentTitle('myblock', null);
55+
assert.equal(out, 'OpenROAD - myblock');
56+
});
57+
});

0 commit comments

Comments
 (0)