runbook
← Archive

AppleScript JS와 isolated world

Chrome의 execute javascript가 페이지 전역을 보지 못하는 이유와, 페이지 쪽에서 코드를 실행하는 방법.

macOS자동화Chrome

AppleScript의 execute javascript로 Chrome 탭의 페이지를 조작할 때, DOM은 그대로 보이는데 페이지가 만든 전역 변수나 함수는 보이지 않습니다. 이 동작을 시험 페이지로 확인하고, 페이지 쪽에서 코드를 실행하는 방법을 정리합니다.

시험 페이지

페이지는 전역 변수 하나와 함수 하나를 만들고, 버튼을 누르면 그 함수의 결과를 화면에 씁니다.

<button id="b">ask</button> <output id="o">-</output>
<script>
  window.appState = { from: 'page' };
  window.ask = () => 'page';
  document.getElementById('b').onclick = () => {
    document.getElementById('o').textContent = window.ask();
  };
</script>

AppleScript에서는 탭을 찾아 JS 문자열을 실행합니다.

tell application "Google Chrome"
  execute (tab id 1959849832 of window id 1959849829) javascript "typeof window.appState"
end tell

확인 결과

확인 항목 AppleScript에서 실행한 코드 결과
DOM 읽기 document.title 페이지 제목 반환
페이지 전역 읽기 typeof window.appState undefined
AppleScript 전역을 페이지에서 읽기 window.fromAS = 1 후 페이지 코드에서 typeof window.fromAS undefined
localStorage localStorage.setItem('k', …) 후 페이지 코드에서 읽기 같은 값
이벤트 document.getElementById('b').click() 페이지 핸들러 실행
함수 바꿔치기 window.ask = () => 'applescript' 후 버튼 클릭 화면에 page
fetch 가로채기 window.fetch = … 후 페이지의 fetch 실행 원래 응답 그대로
실행 사이 상태 한 번 실행에서 window.persist = 42, 다음 실행에서 읽기 number (남아 있음)

DOM · 이벤트 · 저장소는 페이지와 같이 쓰지만, JavaScript 전역 객체는 따로입니다. Chrome 확장 프로그램의 content script가 쓰는 isolated world와 같은 동작입니다.

이 구조 때문에 다음 작업은 AppleScript 쪽 코드만으로는 되지 않습니다.

페이지 쪽에서 실행하기

DOM은 공유되므로 <script> 요소를 문서에 넣으면 그 코드는 페이지의 전역에서 실행됩니다. execute javascript의 반환값은 AppleScript 쪽 결과이므로, 페이지 쪽 결과는 DOM 속성에 적어 두고 다시 읽습니다.

const s = document.createElement('script');
s.textContent = `
  window.confirm = () => true;          // 확인 창을 자동으로 통과
  document.documentElement.dataset.r = typeof window.appState;
`;
document.head.appendChild(s);
s.remove();
document.documentElement.dataset.r   // "object"

같은 방법으로 window.ask를 바꾼 뒤 버튼을 누르면 화면에 바꾼 값이 나타납니다. confirm()을 띄우는 버튼은 이렇게 먼저 window.confirm을 바꾼 뒤 누르면 확인 창 없이 진행됩니다.

CSP가 있는 페이지

콘텐츠 보안 정책(CSP)이 걸린 페이지에서는 넣은 <script>가 막힐 수 있습니다.

정책 그대로 넣은 경우 해결
script-src 'self' 'nonce-…' 오류 없이 실행되지 않음 기존 <script>의 nonce를 복사해 붙이기
require-trusted-types-for 'script' textContent 대입에서 TypeError Trusted Types 정책을 만들어 TrustedScript로 대입
const policy = trustedTypes.createPolicy('cc', { createScript: (x) => x });
const s = document.createElement('script');
s.nonce = document.querySelector('script[nonce]').nonce;
s.textContent = policy.createScript('document.documentElement.dataset.r = window.ask()');
document.head.appendChild(s);

탭 지정

active tab of window 1은 Chrome 창 순서가 바뀌면 다른 탭을 가리킵니다. 여러 작업이 같은 Chrome을 함께 쓰는 환경에서는 URL로 탭을 찾은 뒤 tab id … of window id …로 지정하는 편이 안전합니다.

tell application "Google Chrome"
  repeat with w in windows
    repeat with t in tabs of w
      if URL of t contains "example.com/target" then
        return execute (tab id (id of t) of window id (id of w)) javascript "document.title"
      end if
    end repeat
  end repeat
end tell

정리

하려는 일 방법
DOM 읽기 · 클릭 · 입력 AppleScript 쪽 코드로 가능
페이지 전역 읽기 · 함수 바꾸기 · 요청 가로채기 <script>를 넣어 페이지 쪽에서 실행
페이지 쪽 결과 받기 DOM 속성에 쓰고 다시 읽기
CSP 페이지 nonce 복사, Trusted Types 정책 사용