문제의 출발점
모든 사례와 코드 예시는 공개용으로 일반화했으며, 실제 시스템의 구조와 식별자를 재현하지 않습니다.
레거시 웹 시스템에서는 window.open()으로 업무 단계를 나눈 화면을 자주 볼 수 있다. 한 팝업이 닫힌 뒤 다음 팝업을 여는 흐름이 이어지면 브라우저 정책에 따라 동작이 달라질 수 있다.
실제 화면도 첫 팝업에서 저장한 뒤 현재 창을 닫고 다음 입력 팝업을 여는 구조였다. 선택 건수가 늘어 처리 시간이 길어지면 후속 단계가 열리지 않는 조건을 재현했다.
원인은 팝업 개수가 아니라 사용자 동작과의 거리였다
첫 번째 팝업은 사용자 클릭 안에서 실행되므로 허용될 수 있다. 반면 비동기 저장, 반복 입력과 현재 창 종료 뒤 실행되는 후속 팝업은 최초 사용자 동작과 직접 연결된 호출로 인정되지 않을 수 있다.
아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.
function completeStep() {
saveCurrentStep().then(function (result) {
window.close();
window.opener.openNextStep(result.businessKey);
});
}해결 목표는 팝업 차단을 우회하는 것이 아니라 업무 단계 전환이 새 창 생성에 의존하지 않게 만드는 것이었다.
부모 화면이 흐름을 소유하도록 변경
개선 구조에서는 부모 화면이 Dialog를 열고 내부 iframe 하나를 재사용한다. 실제 화면 ID, URL과 크기 설정은 공개하지 않는다.
아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.
<div id="workflowDialog" hidden>
<iframe id="workflowFrame" title="업무 단계"></iframe>
</div>아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.
function openWorkflow(url) {
const frame = document.getElementById("workflowFrame");
frame.src = url;
openModal("workflowDialog");
}부모는 Dialog의 열기·닫기와 현재 단계를 관리하고, 자식은 자신의 입력과 저장 결과만 관리한다. 화면 간 결합은 opener의 함수 직접 호출 대신 명시적인 메시지 계약으로 제한했다.
postMessage로 단계 전환 이벤트 전달
자식 화면은 저장이 끝나면 일반화한 이벤트와 필요한 최소 데이터만 부모에 보낸다.
아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.
window.parent.postMessage(
{
type: "workflow:advance",
payload: { businessKey: result.businessKey }
},
window.location.origin
);부모는 출처와 메시지 형태를 확인한 뒤 다음 화면으로 전환한다.
아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.
window.addEventListener("message", function (event) {
if (event.origin !== window.location.origin) return;
if (!event.data || typeof event.data.type !== "string") return;
if (event.data.type === "workflow:advance") {
loadNextStep(event.data.payload.businessKey);
}
if (event.data.type === "workflow:complete") {
closeModal("workflowDialog");
reloadParentView();
}
});targetOrigin, event.origin, 메시지 타입과 payload 형태를 함께 확인해 “어디에서 온 어떤 메시지를 처리할 것인가”를 코드에 드러냈다.
기존 팝업 경로를 바로 제거하지 않은 이유
같은 레거시 화면이 iframe과 기존 팝업 양쪽에서 호출될 수 있었다. 영향 범위를 통제하기 위해 실행 환경을 구분해 두 경로를 함께 지원했다.
아래 코드는 문제 해결 구조를 설명하기 위한 pseudocode이며 실제 클래스·테이블·설정 이름과는 무관합니다.
function notifyComplete(businessKey) {
if (window.self !== window.top) {
window.parent.postMessage(
{ type: "workflow:complete", payload: { businessKey } },
window.location.origin
);
return;
}
if (window.opener && !window.opener.closed) {
window.opener.reloadParentView();
}
window.close();
}호환 경로는 불필요한 잔재가 아니라 전환 범위를 통제하는 장치였다.
검증 범위
- 결과 상세 화면에서 다건 등록
- 임시저장 상태에서 이어서 등록
- 첫 단계에서 다음 입력 화면으로 전환
- 완료 후 Dialog 종료와 부모 화면 갱신
- 본사 적용 후 해외법인의 동일 흐름
- 기존 팝업 경로의 회귀 여부
브라우저 팝업 허용 여부와 관계없이 업무 단계가 하나의 Dialog 안에서 이어지고, 기존 팝업 호출부도 깨지지 않는 것을 확인했다.
정리
핵심은 window.open()을 iframe으로 바꾼 기술 자체가 아니다. 다단계 업무 흐름의 소유권을 부모 화면으로 옮기고 자식은 메시지로 상태 전환을 요청하도록 책임을 재설계한 점이다.