AppleScript의 execute javascript로 Chrome 탭의 페이지를 조작할 때, DOM은 그대로 보이는데 페이지가 만든 전역 변수나 함수는 보이지 않습니다.
이 동작을 시험 페이지로 확인하고, 페이지 쪽에서 코드를 실행하는 방법을 정리합니다.
- 환경: macOS, Chrome 157
- 전제: Chrome 메뉴
보기 › 개발자 정보 › Apple Events의 자바스크립트 허용켜짐
시험 페이지
페이지는 전역 변수 하나와 함수 하나를 만들고, 버튼을 누르면 그 함수의 결과를 화면에 씁니다.
<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 쪽 코드만으로는 되지 않습니다.
window.confirm·window.alert를 바꿔 확인 창 건너뛰기fetch·XMLHttpRequest를 가로채 요청 · 응답 보기- React 등 프레임워크가 전역이나 클로저에 둔 상태 읽기
페이지 쪽에서 실행하기
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);
- nonce 정책에서는 막혀도 예외가 발생하지 않으므로, 실행 여부는 DOM 속성으로 확인해야 합니다.
- Trusted Types 정책 이름은 페이지 CSP의
trusted-types허용 목록에 있어야 만들 수 있습니다.
탭 지정
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 정책 사용 |