NOMO.asia

최근 Userscript 개발 중 브라우저(확장기능) 호환성에 의해 문제가 생겼던 경험을 적어본다. 비교한 버전은 아래와 같다.

Firefox 54.0.1, Greasemonkey 3.11 / Chrome 59, Tampermonkey 4.3.6


상황 가정

  1. Userscript 를 적용할 페이지에서 로드하는 js 파일 내에 fA, fB 라는 이름의 함수(function) 가 존재한다.
  2. fA 함수의 내부에서는 fB를 호출한다.
  3. 내가 Userscript를 이용하여 덮어씌우고 싶은(Override) 함수는 fB 함수이다.


unsafeWindow를 이용하여 UserScript 함수 덮어쓰기

공통 사항

먼저 Metablock에 아래 문구를 추가했다. (https://wiki.greasespot.net/@grant)


// @grant       unsafeWindow

// @run-at      document-start

// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js


@run-at document-start 문구는 가능한 한 빨리 UserScript 가 실행되도록 하는 역할을 한다. 이 문구를 추가한 이유는 내 경우에 이 조건을 추가하지 않으면 경우에 따라 hijacking이 될 때도 있고 안 될때도 있는 등 동작이 안정적이지 않았다.


더불어 document-start 옵션을 쓰면 Userscript를 적용할 웹사이트에 존재하는 jQuery 라이브러리가 로드되기 전에 Userscript가 실행되므로 웹사이트에서 로드하는 jQuery 라이브러리를 사용할 수 없다. 따라서 @require 에 jQuery 라이브러리 주소를 추가해주었다.

참고할만한 링크: https://stackoverflow.com/questions/12146445/jquery-in-greasemonkey-1-0-conflicts-with-websites-using-jquery


Chrome - Tampermonkey 의 경우

이 경우 큰 문제가 없다. 아래와 같이 하면 문제 없이 작동한다. 내가 바꾸고싶은 함수는 fB 뿐이므로 fB의 내용만 바꿔주었다.


$(document).ready(function(){

unsafeWindow.fB = function() {

            // 새로운 function 내용

 console.log("fB was hijacked!");

 };

})


run-at : document-start 조건을 추가하였으므로, document ready 내에 hijacking 코드를 추가해주어야 제대로 적용된다.



Firefox - Greasemonkey 의 경우

이 경우 Chrome 의 Tampermonkey 와 같이 하면 문제가 발생할 수 있다. (잘 될 수도 있다.)

console 창에는 아래와 같은 메시지가 나타난다.


permission denied to access property "fB"


Userscript 가 담긴 js 파일은 local drive에 존재하고, override 된 fB 의 내용은 이 js 파일 내에 있으므로, 웹사이트에 원래 존재하던 fA라는 함수가 local 에 존재하는 js 파일의 fB 함수에 접근하려고 함으로써 에러가 나는 듯싶다. (추정일 뿐 확실하지는 않다)


정확한 이유는 나중에 알게 되었는데, [unsafeWindow 의 의미와 사용법] 이라는 글을 참고하면 된다.


Firefox 는 아래와 같이 해주면 잘 된다.


$(document).ready(function(){

newfB = function() {

            // 새로운 function 내용

             console.log("fB was hijacked!");

};

unsafeWindow.fB = exportFunction (newfB, unsafeWindow);


newfA = function() {

newfB(); // fB 를 newfB로 대체한다.

           console.log("fA was hijacked!");

};

unsafeWindow.fA = exportFunction (newfA, unsafeWindow);

})


exportFunction 라는 함수는 일반적인 브라우저에서 보편적으로 지원하는 함수는 아니다. 참고할만한 링크: https://blog.mozilla.org/addons/2014/04/10/changes-to-unsafewindow-for-the-add-on-sdk/


만약 fA 와 fB 를 호출하는 다른 함수가 있고 유사한 문제가 발생한다면 모두 위와 같이 처리해주어야 할 수 있다.



브라우저 호환성 확보

한 Userscript 로 Firefox 와 Chrome 에서 모두 사용할 수 있게 하기 위하여, 브라우저를 체크하여야 한다. 사실 브라우저 호환성이라기 보다는, 각 브라우저마다 존재하는 확장기능의 호환성이라고 봐야할 듯싶다. Chrome의 Tampermonkey 같은 경우 다음과 같은 API를 제공한다. GM_info 의 경우 별도로 @grant 에 어떤 추가를 필요로하지 않는다.


GM_info.scriptHandler === "Tampermonkey"; // True


그러나 Firefox 의 Greasemonkey 같은 경우 위와 같이 사용하면 undefined 값이 리턴된다.


내 경우에는 jquery browser(http://jquery.thewikies.com/browser/) 를 사용해주었다. 해당 라이브러리를 @require 등으로 로드한 뒤 다음과 같이 변수에 저장하여 조건문에서 활용한다.


var web_browser = J$.browser.name;


if(web_browser === 'chrome'){

// Chrome 용 코드

}

else if(web_browser === 'firefox'){

// Firefox 용 코드

}

else

{

// unsafeWindow 를 사용하지 않고 우회하여 사용할 수 있는 방법

}


unsafeWindow 를 사용하지 않고 우회하여 사용할 수 있는 방법 중 하나는, 함수나 이벤트 내에서 사용하는 class, id 명을 모두 변경하여 해당 class 나 id 로 인해 함수가 동작하지 않도록 한 뒤, Userscript 내에서 모두 새로 다시 작성하는 것이다(설령 그것이 단순히 원본 코드의 복사&붙여넣기 라도). 또는 Object 생성을 event로 체크하여 해당 Object 가 생성될 때 수정하는 방법도 있겠다. 비효율적이지만 이정도가 유지보수하기 쉬운 방법인 것 같다.



이외 호환성 문제

Chrome 에서는 별 문제가 없는데 Firefox 에서만 jQuery 함수가 충돌하는 경우가 있다. 웹사이트에 원래 존재하던 함수 중 Userscript 와 직접적인 관계에 없는 jQuery 함수를 사용하는 일부 함수들에서 아래와 같은 문제가 발생한다. 왜 전부도 아니고 일부 함수에서만 이러한 문제가 발생하는지는 모르겠다.


permission denied to access property "reload"

permission denied to access property "removeClass"


역시 앞에서와 비슷한 이유로 추정되는데, 웹사이트쪽 함수에서 Userscript 에서 불러온 jQuery 를 사용하려고 하다가 오류가 나는 것 같다.


Userscript 내에 noConflict 를 선언하거나 아예 jQuery 라이브러리의 모든 호출자를 J$, JjQuery 등으로 변경해 Userscript 내에 직접 삽입해도 작동하지 않더라. 혹은 웹사이트에서 사용하는 jQuery를 Userscript 에서 불러온 것을 사용하게도 시도해봤는데, 아마 어떤 실수가 있었던지 잘 안됐다.


내 경우에는 충돌하는 함수가 몇개 되지 않아서 그냥 unsafeWindow 를 이용해 Userscript 내에 재정의 해주는 방법으로 해결했다 -_-;;