기여 목록 / 7969697
[security] Render the origin display with Lit
DevToolsrefactordevtools/devtools-frontendMerged
SecurityOriginView의 security state 아이콘과 Origin URL 표시를 Lit 템플릿으로 전환했습니다.
문제 설명
SecurityOriginView는 security state 아이콘과 URL 표시를 DOM 생성·수정 코드로
구성하고 있었습니다. 이 부분을 Lit 템플릿으로 옮겨 생성 시점과 security state
변경 시점에 같은 렌더링 함수를 사용하도록 했습니다.
해결 내용
- URL을 Lit 템플릿으로 만드는
renderHighlightedUrl()을 추가했습니다.://가 없는 URL은 일반 텍스트로, 있는 URL은security state에 따라 scheme을 강조하도록 했습니다. #renderOriginDisplay()에서 아이콘과 URL을 함께 렌더링하고, 생성자와setSecurityState()에서 호출하도록 했습니다.SecurityPanelSidebar는Element를 필요로 하므로, 기존createHighlightedUrl()은 Lit 결과를 렌더링한 요소를 반환하는 함수로 유지했습니다.- URL 표시를 확인하는 단위 테스트 2개와
AUTHORS항목을 추가했습니다.
전체 코드 diff는 다음과 같습니다.
--- a/front_end/panels/security/SecurityPanel.test.ts
+++ b/front_end/panels/security/SecurityPanel.test.ts
@@ -14,6 +14,32 @@
import * as Security from './security.js';
const {urlString} = Platform.DevToolsPath;
+
+describe('createHighlightedUrl', () => {
+ it('renders a URL without a scheme separator as plain text', () => {
+ const highlightedUrl =
+ Security.SecurityPanel.createHighlightedUrl(urlString`foo.bar`, Protocol.Security.SecurityState.Secure);
+
+ assert.strictEqual(highlightedUrl.textContent, 'foo.bar');
+ assert.isFalse(highlightedUrl.classList.contains('highlighted-url'));
+ });
+
+ it('renders a URL with a highlighted scheme', () => {
+ const highlightedUrl =
+ Security.SecurityPanel.createHighlightedUrl(urlString`https://foo.bar`, Protocol.Security.SecurityState.Secure);
+
+ assert.strictEqual(highlightedUrl.textContent, 'https://foo.bar');
+ assert.isTrue(highlightedUrl.classList.contains('highlighted-url'));
+
+ const scheme = highlightedUrl.querySelector('.url-scheme-secure');
+ assert.isNotNull(scheme);
+ assert.strictEqual(scheme.textContent, 'https');
+
+ const schemeSeparator = highlightedUrl.querySelector('.url-scheme-separator');
+ assert.isNotNull(schemeSeparator);
+ assert.strictEqual(schemeSeparator.textContent, '://');
+ });
+});
describeWithEnvironment('SecurityPanelSidebarTree', () => {
describe('updateOrigin', () => {
--- a/AUTHORS
+++ b/AUTHORS
@@ -49,6 +49,7 @@
Han Xu <suyanhanx@gmail.com>
Helmut Januschka <helmut@januschka.com>
Henry Lim <henry@limhenry.xyz>
+Hojeong Park <parkhj062@gmail.com>
Jacky Hu <flameddd@gmail.com>
Jake Mulhern <mulherje@gmail.com>
Jan Keitel <jan.keitel@gmail.com>
--- a/front_end/panels/security/SecurityPanel.ts
+++ b/front_end/panels/security/SecurityPanel.ts
@@ -12,7 +12,7 @@
import * as NetworkForward from '../../panels/network/forward/forward.js';
import {createIcon, type Icon} from '../../ui/kit/kit.js';
import * as UI from '../../ui/legacy/legacy.js';
-import {html, render} from '../../ui/lit/lit.js';
+import {html, render, type TemplateResult} from '../../ui/lit/lit.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import lockIconStyles from './lockIcon.css.js';
@@ -510,26 +510,31 @@
return createIcon(iconName, className);
}
-export function createHighlightedUrl(url: Platform.DevToolsPath.UrlString, securityState: string): Element {
+function renderHighlightedUrl(url: Platform.DevToolsPath.UrlString, securityState: string): TemplateResult {
const schemeSeparator = '://';
const index = url.indexOf(schemeSeparator);
// If the separator is not found, just display the text without highlighting.
if (index === -1) {
- const text = document.createElement('span');
- text.textContent = url;
- return text;
- }
-
- const highlightedUrl = document.createElement('span');
- highlightedUrl.classList.add('highlighted-url');
+ return html`<span>${url}</span>`;
+ }
+
const scheme = url.substr(0, index);
const content = url.substr(index + schemeSeparator.length);
- highlightedUrl.createChild('span', 'url-scheme-' + securityState).textContent = scheme;
- highlightedUrl.createChild('span', 'url-scheme-separator').textContent = schemeSeparator;
- highlightedUrl.createChild('span').textContent = content;
-
- return highlightedUrl;
+
+ return html`
+ <span class="highlighted-url">
+ <span class=${`url-scheme-${securityState}`}>${scheme}</span>
+ <span class="url-scheme-separator">${schemeSeparator}</span>
+ <span>${content}</span>
+ </span>`;
+}
+
+export function createHighlightedUrl(url: Platform.DevToolsPath.UrlString, securityState: string): Element {
+ const fragment = document.createDocumentFragment();
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
+ render(renderHighlightedUrl(url, securityState), fragment);
+ return fragment.firstElementChild as Element;
}
export interface ViewInput {
@@ -1315,11 +1320,14 @@
}
export class SecurityOriginView extends UI.Widget.VBox {
- private readonly originLockIcon: HTMLElement;
+ readonly #origin: Platform.DevToolsPath.UrlString;
+ readonly #originDisplay: HTMLElement;
+
constructor(origin: Platform.DevToolsPath.UrlString, originState: OriginState) {
super({jslog: `${VisualLogging.pane('security.origin-view')}`});
this.registerRequiredCSS(originViewStyles, lockIconStyles);
this.setMinimumSize(200, 100);
+ this.#origin = origin;
this.element.classList.add('security-origin-view');
@@ -1328,13 +1336,8 @@
titleDiv.textContent = i18nString(UIStrings.origin);
UI.ARIAUtils.markAsHeading(titleDiv, 1);
- const originDisplay = titleSection.createChild('div', 'origin-display');
- this.originLockIcon = originDisplay.createChild('span');
- const icon = getSecurityStateIconForDetailedView(
- originState.securityState, `security-property security-property-${originState.securityState}`);
- this.originLockIcon.appendChild(icon);
-
- originDisplay.appendChild(createHighlightedUrl(origin, originState.securityState));
+ this.#originDisplay = titleSection.createChild('div', 'origin-display');
+ this.#renderOriginDisplay(originState.securityState);
const originNetworkDiv = titleSection.createChild('div', 'view-network-button');
const originNetworkButton = UI.UIUtils.createTextButton(i18nString(UIStrings.viewRequestsInNetworkPanel), event => {
@@ -1573,10 +1576,20 @@
}
setSecurityState(newSecurityState: Protocol.Security.SecurityState): void {
- this.originLockIcon.removeChildren();
- const icon = getSecurityStateIconForDetailedView(
- newSecurityState, `security-property security-property-${newSecurityState}`);
- this.originLockIcon.appendChild(icon);
+ this.#renderOriginDisplay(newSecurityState);
+ }
+
+ #renderOriginDisplay(securityState: Protocol.Security.SecurityState): void {
+ const icon =
+ getSecurityStateIconForDetailedView(securityState, `security-property security-property-${securityState}`);
+
+ // clang-format off
+ // eslint-disable-next-line @devtools/no-lit-render-outside-of-view
+ render(html`
+ ${icon}
+ ${renderHighlightedUrl(this.#origin, securityState)}
+ `, this.#originDisplay);
+ // clang-format on
}
}테스트 방법
SecurityPanel.test.ts에 다음 두 단위 테스트를 추가했습니다.
foo.bar처럼://가 없는 URL은 강조 없이 표시되는지 확인https://foo.bar는 URL 텍스트와 scheme의 강조 클래스,://표시가 올바른지 확인
리뷰 과정에서 DOM 구조를 출력하는 Chromium 웹 테스트의 실패를 확인했습니다. 리뷰어 안내에 따라 CL 8038580에서 기대값을 임시 조정하고, 변경이 Chromium에 반영된 뒤 CL 8042189에서 기대 결과를 갱신했습니다. 이 CL은 CQ를 거쳐 2026년 7월 4일(한국 시간) DevTools main에 병합되었습니다.
배운 점
- Lit 전환 시 호출부가 템플릿과 DOM 요소 중 무엇을 필요로 하는지 확인해야 합니다. Origin 표시에는 템플릿을 직접 사용하고, Sidebar에는 요소를 반환하는 함수를 유지했습니다.
- DOM을 출력하는 테스트는 Lit이 추가하는 주석 노드나 요소 구조 변경에도 영향을 받으므로, Chromium 쪽 기대 결과도 함께 확인해야 합니다.