OSSCA Chromium

기여 목록 / 8467003

Migrate devtools/security/mixed-content-active-and-passive-reload.js to a unit test

DevToolstestdevtools/devtools-frontendMerged

Chromium의 mixed-content-active-and-passive-reload.js를 DevTools unit test로 옮겼습니다. request가 기록되면 active·passive mixed content의 reload 안내가 Network panel 링크로 바뀌는지 검증합니다.

문제 설명

기존 테스트는 request 기록 전후의 DOM 출력을 기대 결과와 비교해, DOM 구조 변경에도 기대 결과 갱신이 필요했습니다. 이를 unit test로 옮겨 안내 문구의 전환을 직접 검증하도록 했습니다.

해결 내용

  • SecurityPanel.test.ts에 unit test 1개를 추가했습니다.
  • active·passive mixed content가 있는 security state를 전달한 뒤, 두 설명에 reload 안내가 표시되는지 확인했습니다.
  • 같은 security state 이벤트를 다시 전달하고, OptionallyBlockable과 Blockable request의 RequestFinished 이벤트를 순서대로 전달했습니다.
  • 두 안내가 각각 View 1 request in Network panel 문구와 role="link"를 갖는 링크로 바뀌는지 검증했습니다.
  • request 기록 전후에 설명이 2개이며, 각 설명의 상태 class·제목·본문이 유지되는지도 확인했습니다.

기존 Chromium 테스트와 기대 결과 파일의 제거는 후속 CL 8483194에서 진행했습니다.

전체 코드 diff는 다음과 같습니다.

diff --git a/front_end/panels/security/SecurityPanel.test.ts b/front_end/panels/security/SecurityPanel.test.ts
index a29ad99..889b29a 100644
--- a/front_end/panels/security/SecurityPanel.test.ts
+++ b/front_end/panels/security/SecurityPanel.test.ts
@@ -901,6 +901,79 @@
     assert.strictEqual(requestsLink.getAttribute('role'), 'link');
   });

+  it('replaces active and passive mixed content reload prompts with request links when requests are recorded', () => {
+    const securityPanel = Security.SecurityPanel.SecurityPanel.instance({forceNew: true});
+    renderElementIntoDOM(securityPanel);
+    const securityModel = target.model(Security.SecurityModel.SecurityModel);
+    assert.exists(securityModel);
+
+    const pageVisibleSecurityState = new Security.SecurityModel.PageVisibleSecurityState(
+        Protocol.Security.SecurityState.Neutral, null, null, ['displayed-mixed-content', 'ran-mixed-content']);
+    securityModel.dispatchEventToListeners(Security.SecurityModel.Events.VisibleSecurityStateChanged,
+                                           pageVisibleSecurityState);
+
+    function assertMixedContentExplanations() {
+      const explanations = securityPanel.mainView.contentElement.querySelectorAll<HTMLElement>('.security-explanation');
+      assert.lengthOf(explanations, 2);
+      const [activeExplanation, passiveExplanation] = explanations;
+
+      assert.isTrue(activeExplanation.classList.contains('security-explanation-insecure'));
+
+      const activeExplanationTitle = querySelectorErrorOnMissing(activeExplanation, '.security-explanation-title');
+      assert.strictEqual(activeExplanationTitle.textContent, 'Resources - active mixed content');
+
+      const activeExplanationText = querySelectorErrorOnMissing(activeExplanation, '.security-explanation-text');
+      assert.include(activeExplanationText.textContent,
+                     'You have recently allowed non-secure content (such as scripts or iframes) to run on this site.');
+
+      assert.isTrue(passiveExplanation.classList.contains('security-explanation-neutral'));
+
+      const passiveExplanationTitle = querySelectorErrorOnMissing(passiveExplanation, '.security-explanation-title');
+      assert.strictEqual(passiveExplanationTitle.textContent, 'Resources - mixed content');
+
+      const passiveExplanationText = querySelectorErrorOnMissing(passiveExplanation, '.security-explanation-text');
+      assert.include(passiveExplanationText.textContent, 'This page includes HTTP resources.');
+      return [activeExplanation, passiveExplanation];
+    }
+
+    // At this point, the page has mixed content but no mixed requests have been recorded,
+    // so the user should be prompted to refresh.
+    const explanationsBeforeReload = assertMixedContentExplanations();
+    for (const explanation of explanationsBeforeReload) {
+      const reloadPrompt = querySelectorErrorOnMissing(explanation, '.security-mixed-content');
+      assert.strictEqual(reloadPrompt.textContent, 'Reload the page to record requests for HTTP resources.');
+    }
+
+    // Now simulate a refresh.
+    securityModel.dispatchEventToListeners(Security.SecurityModel.Events.VisibleSecurityStateChanged,
+                                           pageVisibleSecurityState);
+    const networkManager = securityModel.networkManager();
+    const passiveRequest = createNetworkRequest({
+      url: 'http://foo.test',
+      documentURL: 'https://foo.test',
+      frameId: '0',
+      loaderId: '0',
+    });
+    passiveRequest.mixedContentType = Protocol.Security.MixedContentType.OptionallyBlockable;
+    networkManager.dispatchEventToListeners(SDK.NetworkManager.Events.RequestFinished, passiveRequest);
+
+    const activeRequest = createNetworkRequest({
+      url: 'http://foo.test',
+      documentURL: 'https://foo.test',
+      frameId: '0',
+      loaderId: '0',
+    });
+    activeRequest.mixedContentType = Protocol.Security.MixedContentType.Blockable;
+    networkManager.dispatchEventToListeners(SDK.NetworkManager.Events.RequestFinished, activeRequest);
+
+    const explanationsAfterReload = assertMixedContentExplanations();
+    for (const explanation of explanationsAfterReload) {
+      const requestsLink = querySelectorErrorOnMissing(explanation, '.security-mixed-content');
+      assert.strictEqual(requestsLink.textContent, 'View 1 request in Network panel');
+      assert.strictEqual(requestsLink.getAttribute('role'), 'link');
+    }
+  });
+
   it('shows origins with blockable and optionally blockable resources in the sidebar', async () => {
     const securityPanel = Security.SecurityPanel.SecurityPanel.instance({forceNew: true});

테스트 방법

CQ를 통해 2026년 9월 29일(한국 시간) DevTools main에 병합되었습니다.

배운 점

상태가 바뀌는 UI는 이벤트 전달 순서를 유지하고, 변경 전후를 모두 검증해야 합니다. active·passive mixed content의 안내가 각각 전환되는지 확인했습니다.

참고 자료