네이버 블로그 자동 발행에서 링크가 문장에 붙어버린 이유 — 캐럿 위치 버그를 재현한 과정

·

네이버 블로그에 발행된 글을 확인하다가, 본문 마지막 문장 뒤에 내부 링크 URL이 줄바꿈 없이 그대로 붙어 있는 걸 발견했다.

...초과분에 대해 부당이득으로 반환을 청구할 수 있다는 게 대법원 판단입니다.https://blog.naver.com/ms_sh0728/224362568517

문장이 끝나는 마침표 바로 뒤에 https://...가 시작된다. 그 아래 링크 미리보기 카드는 정상적으로 만들어져 있었으니, 겉보기엔 “그럭저럭 봐줄 만한” 상태였다. 하지만 다른 글에서는 같은 링크가 새 줄에서 시작하고 있었다 — 같은 코드가 왜 다르게 동작했는지부터 확인해야 했다.

이 크롬 확장은 CDP(Chrome DevTools Protocol)로 신뢰된 입력을 흉내 내서 네이버 스마트에디터를 자동 조작한다. 본문에 내부 링크를 넣는 함수는 이런 흐름이었다.

async function pasteLink(block, log) {
  // ...클립보드에 URL 쓰기...
  await cdp([...caretToEndActions(), { a: "paste" }, { a: "wait", ms: 1500 }]);
  // ...오지링크 카드 변환 확인...
}

caretToEndActions()는 본문의 마지막 문단을 다시 찾아서, 그 문단의 마지막 글자 바로 뒤 지점에 캐럿을 두는 액션 배열을 만든다.

function caretToEndActions() {
  const last = lastBodyParagraph();
  const point = caretEndPoint(last.el);
  return [
    { a: "click", x: point.x, y: point.y },
    { a: "wait", ms: 80 },
    { a: "key", key: "End" },
  ];
}

문제는 여기 있었다. 캐럿을 “마지막 글자 바로 뒤”에 두고 그 자리에 곧바로 붙여넣기(paste)를 실행하니, URL이 그 문장의 연장선으로 삽입된다. 문단을 나누는 동작이 코드 어디에도 없었다.

왜 어떤 글에서는 줄바꿈이 됐나

발행된 다른 글을 다시 보니, 그 글은 본문 마지막 줄이 원래부터 빈 줄(문단 구분)로 끝나 있었다. lastBodyParagraph()가 그 빈 문단을 “마지막 문단”으로 찾아서 캐럿을 그 빈 문단에 뒀고, 결과적으로 링크가 새 줄에서 시작한 것처럼 보였을 뿐이다. 즉 버그는 항상 같은 자리에 있었는데, 초안의 마지막 줄이 우연히 비어 있었는지 아닌지에 따라 증상이 있다 없다 했던 것이다.

고친 방법 — 이미 있던 CDP Enter 패턴을 재사용

같은 파일의 태그 입력 부분에는 CDP로 Enter 키를 보내는 코드가 이미 있었다.

{ a: "key", key: "Enter" }

네이버 스마트에디터는 일반 합성 이벤트(isTrusted: false)를 무시하기 때문에, 이 확장은 처음부터 모든 키 입력을 CDP로 보내는 구조였다. 새 메커니즘을 만들 필요 없이, 캐럿 위치 조정과 붙여넣기 사이에 이 Enter 액션만 끼워 넣으면 됐다.

async function pasteLink(block, log) {
  // ...클립보드에 URL 쓰기...
  await cdp([...caretToEndActions(), { a: "key", key: "Enter" }, { a: "wait", ms: 200 }]);
  await cdp([...caretToEndActions(), { a: "paste" }, { a: "wait", ms: 1500 }]);
  // ...오지링크 카드 변환 확인...
}

caretToEndActions()를 두 번 호출하는 게 핵심이다. 이 함수는 매번 라이브 DOM을 다시 조회해서 “지금 시점의 마지막 문단”을 찾기 때문에, Enter로 새 문단을 만든 다음 다시 호출하면 그 새 문단을 정확히 찾아낸다. 캐럿 위치를 캐싱해서 재사용하는 구조였다면 이 수정은 통하지 않았을 것이다.

정리

항목내용
증상본문 링크가 앞 문장에 줄바꿈 없이 이어붙음
겉으로 안 드러난 이유오지링크 카드 변환은 문단 중간에서도 정상 동작
원인캐럿을 마지막 글자 뒤에 두고 문단 구분 없이 바로 붙여넣기
증상이 있다 없다 했던 이유초안 마지막 줄이 우연히 빈 문단이면 증상이 안 보임
해결캐럿 위치 조정 → CDP Enter → 캐럿 위치 재조정 → 붙여넣기

발행 결과를 눈으로 직접 확인하지 않았다면 이 버그는 계속 “가끔 괜찮고 가끔 아닌” 상태로 남아 있었을 것이다. 자동화 코드가 만들어낸 결과물은, 자동화 로직만 읽어서는 알 수 없는 문제를 숨기고 있을 때가 있다.

자주 묻는 질문

왜 이 버그가 그동안 발견되지 않았나요?

네이버 스마트에디터는 붙여넣은 URL을 문단 중간에 있어도 오지링크(링크 미리보기 카드)로 정상 변환합니다. 그래서 카드 자체는 항상 잘 만들어졌고, 그 카드 앞에 줄바꿈이 빠져 있다는 건 실제 발행된 글의 화면을 눈으로 봐야만 알 수 있었습니다.

캐럿을 마지막 글자 뒤에 두는 게 왜 문제인가요?

이 코드의 캐럿 포지셔닝 함수는 마지막 문단의 마지막 글자 바로 뒤 지점을 계산해서 캐럿을 그 자리에 둡니다. 그 상태로 클립보드 내용을 바로 붙여넣으면, 붙여넣는 내용이 새 문단이 아니라 그 문장의 연장선에 삽입됩니다. 문단을 나누는 동작(Enter)이 따로 없으면 당연한 결과입니다.

왜 Enter 키를 일반적인 방법이 아니라 CDP로 보냈나요?

이 확장은 네이버 스마트에디터가 신뢰되지 않은(isTrusted: false) 합성 이벤트를 무시하기 때문에, 모든 키 입력·붙여넣기를 크롬 디버거 프로토콜(CDP)로 보내는 구조를 이미 쓰고 있습니다. 새로 만든 코드가 아니라 태그 입력 등 다른 곳에서 이미 검증된 패턴을 그대로 재사용한 것입니다.

#크롬확장#CDP#네이버블로그#자동화#버그