NOMO.asia

iframe 내에서 작동하는 Userscript 를 코드를 작성하다가, 브라우저 확장기능 별로 iframe 내에서 Userscript 의 호출 여부가 다르다는 것을 알게되었다. 무엇보다 Firefox 의 Greasemonkey 에서는 iframe 내에서 require로 jquery를 불러와도 사용이 안 되는 경우가 있어서 다른 확장기능에서는 어떤지 테스트를 해보고 싶었다. 어떤 웹사이트에서 테스트 했는지는 비밀이다. 모든 웹사이트에서 아래와 같은 상황이 생기는 것은 아닐 것이다.



테스트한 웹사이트 구조

1. main frame ( url.com )

 └ 2. iframe ( url.com/iframe.php )

   └ 3. iframe ( about:blank, 즉 iframe의 src 값이 존재하지 않음. 동적으로 생기므로 생성까지 약간의 딜레이 존재함 )


최상단에 main frame 이 있고, 그 안에 첫번째 iframe 이 있고, 그 안에 src가 존재하지 않는 iframe 이 존재하는 구조이다.

마지막 iframe 은 외부 css, js 의 영향을 받지 않기 위한 목적으로 iframe 의 형태를 취한 것이라 src 값이 존재하지 않는다.



테스트한 Userscript 코드

// ==UserScript==
// @name        iframeTest
// @namespace   iframeTest
// @description iframe 테스트용 코드
// @include     *.url.com*
// @version     0.0.1
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js
// @run-at      document-start
// ==/UserScript==
/*jshint multistr: true */

console.log('now : ',window.location);

if (typeof jQuery == 'undefined') {
  console.log('jquery가 존재하지 않음');
  console.log(document.getElementsByTagName('head'));
  var script = document.createElement('script');
  script.type = "text/javascript";
  script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js";
  document.getElementsByTagName('head')[0].appendChild(script);
  console.log('jquery:', typeof jQuery);
}
else{
  console.log('jquery가 존재함');
  console.log(window.jQuery);
  console.log($(document).find('head').html());
}

// 1초 뒤에 확인
setTimeout(function(){
  console.log('jquery:', typeof jQuery);
},1000)


결과 정리

① Firefox - Greasemonkey

run-at 값에 뭘 주느냐에 따라 결과가 다르다.

  1. document-start 의 경우
    main frame 과 첫 번째 iframe 에서 Userscript가 호출된다.
    main frame 에서는 모든 기능이 정상적으로 동작한다.
    첫 번째 iframe 에서 스크립트가 호출된 직후에는 jquery 가 존재한다고 표시된다. 하지만 1초 뒤에 다시 체크해보면 jquery 가 undefined 상태가 된다. 내가 테스트 한 웹사이트에서만 이런 증상이 발생할 것 같기는 한데, 동일한 구조를 갖는 다른 웹사이트에서 테스트 해보지는 않았다. 여하튼 이러한 경우 jquery 함수를 사용하면 오류가 발생하며, jquery 를 사용하려면 Metadata block의 require 에 의존해서는 안 되고 별도의 호출 코드를 삽입하고 setTimeout 으로 딜레이를 주는 등 귀찮은 작업을 추가적으로 해야 한다.
  2. document-end 의 경우
    main frame 에서만 호출된다. jquery 사용에 문제 없다.
  3. document-idle 의 경우
    main frame 과 첫 번째 iframe 에서 호출된다. jquery 사용에 문제 없다.

② Firefox - Violentmonkey

모든 프레임에서 Userscript가 호출된다. run-at 값에 따라 결과가 달라지지 않는다. jquery 사용에 문제 없다.

③ Chrome - Tampermonkey

main frame 과 첫 번째 iframe 에서 Userscript가 호출된다. run-at 값에 따라 결과가 달라지지 않는다. jquery 사용에 문제 없다.


Chrome 의 Violentmonkey 에서는 테스트해보지 않았지만 큰 문제는 없을 것 같다.


2019-01-18 추가:

@include 를 *.url.com* 이 아니라 * 로 설정하여 모든 웹사이트에서 동작하도록 설정하면, Tampermonkey 에서도 두 번째 iframe 에서 호출됨을 확인하였다. 그 외 about:blank 등의 키워드는 먹히지 않았다.



최상단 프레임에서만 Userscript 를 동작시키고 싶은 경우

참고로 iframe 내에서 Userscript가 호출이 되는 것은 전혀 문제가 되지 않는다.
만약 내가 최상위 프레임에서만 Userscript 를 동작시키고 싶다면 아래와 같은 방법이 있다.
  1. Metadata block에 @noframes 를 삽입한다.
  2. 코드 내에서 window.top === window.self 가 true 이면 main frame, false 이면 Iframe 이다. 코드 최상단에서 iframe 인지 여부를 체크하고, iframe의 경우 return 하여 코드를 중지시키면 된다.
  3. location.href 을 체크, iframe 에 해당하는 주소인 경우 return 시킨다. (iframe 주소에 해당하는 리스트를 작성 필요가 있다)

만약 iframe 내에서 Userscript가 호출되지 않는다면?

cross-origin 정책을 위반하지 않는 경우에 최상단 프레임에서 아래와 같이 코드를 작성하면 iframe 내부에 접근할 수 있기는 하다.
//첫번째 iframe 내부 접근
$('#iframe_id1').contents().first();

//두번째 iframe 내부 접근
$('#iframe_id1').contents().first().find('#iframe_id2').contents().first();
그렇지만 iframe element가 동적으로 생성되는 경우 생성되었는지 여부를 매번 체크해주어야 하고, 첫번째 iframe이 갱신되면 두번째 iframe 을 다시 찾아줘야 되니까 귀찮다. 무엇보다 이벤트를 바인드 하는게 제일 귀찮다(생각하는 대로 매번 잘 안 된다!!!!).


따라서 iframe 내에서 Userscript가 호출되기만 하면, 코드를 각 프레임 별로 작성하고
window.top 이나 location.href 으로 현재 어떤 frame에 있는지 여부를 파악한 다음
해당 프레임에 맞는 코드만 실행시키고 아니면 return 시키면 되므로 코드 작성이 간단해진다.
프레임 간에 변수를 주고 받는 것도 큰 문제는 없는듯하다.


결론

Firefox의 경우 Violentmonkey 확장 기능을 사용하는 것이 최고다.

Greasemonkey 는 Firefox Quantum 업데이트 이후 구려졌으므로 쓰지말자.