크롬 익스텐션에서 RequireJS 사용하기


발생일: 2012.10.27

문제:
정말 오랜만에 한가한 주말이다.
오후부터, 한참을 미뤄뒀던 네이버 영어사전 크롬 익스텐션 리팩토링 작업을 시작했다.

처음 만들었을 때엔 의존성을 해결하기 위해 직접 프레임워크를 만들었었는데,
문서도 없고 내멋대로 만든 거라 다른 개발자들이 참여하기도 어려운 것 같았다.

그래서 이번엔 RequireJS를 사용해보려고 한다.


백그라운드 코드부터 작업하기 시작했고, 아무 매끄럽게 잘 적용할 수 있었다.
헌데, 컨텐트 스크립트에 적용하려고 하니 좀 문제가 있다.

먼저, 보안 이슈 때문에 파일이 로드되지 않았고,
다음엔 스크립트를 로드해와도 제대로 로드되지 않았다.

어떻게 해결하면 될까?


해결책:
컨텐트 스크립트에서 RequireJS를 사용하는 데엔 두 가지 문제가 있었다.


1. 익스텐션 폴더의 스크립트 로드하기
매니페스트 파일에서는 컨텐트 스크립트에 require.js 파일만 추가하고,
다른 스크립트 파일들은 비동기로 불러오려고 한다.

이 때, 콘솔에 아래와 같은 메시지가 출력되면서 에러를 발생한다.

  Denying load of chrome://xxxxxxx
  Resources must be listed in the web_accessible_resources manifest key in order to be loaded by pages outside the extension.

매니페스트 파일이 v2로 업데이트 되면서,
컨텐트 스크립트에서 익스텐션의 폴더의 리소스에 접근하려면
접근하려는 파일 목록을 화이트 리스트 방식으로 명시해줘야 하기 때문이다.

아래와 같이 비동기로 불러올 스크립트 목록을 명시해준다.
    {
      ...
      "web_accessible_resources": [
        "필요한 파일.js", // 파일명을 명시한다.
        "js/*" // 와일드카드를 쓸 수도 있다.
      ],
      ...
    }

자세한 내용은 아래 가이드를 참고한다.


2. 샌드박스 문제 해결하기
크롬 익스텐션의 컨텐트 스크립트는, 웹페이지 스크립트와는 독립된 샌드박스를 가지고 있다.
두 스크립트의 환경은 독립되어 있기 때문에 변수를 공유할 수 없다.
하지만, 두 샌드박스 모두 DOM에는 자유롭게 접근할 수 있다.

RequireJS가 비동기로 스크립트를 로드하는 일반적인 방법은 DOM에 script 태그를 추가하는 방식이다.
일반적인 환경에서는 문제가 되지 않지만,
RequireJS가 컨텐트 스크립트의 샌드박스에서 로드할 때에는 상황이 다르다.

컨텐트 스크립트에 require.js를 추가하면 RequireJS는 컨텐트 스크립트의 샌드박스에 존재한다.
하지만, require()로 다른 스크립트 파일을 로드하면,
웹페이지와 공유하고 있는 DOM에 스크립트 태그를 추가하게 되고,
로드된 스크립트는 웹페이지 스크립트의 샌드박스에 존재한다.

이처럼 다른 샌드박스에 스크립트가 로드되는 문제를 해결하기 위해,
컨텐트 스크립트에서는 XHR로 다른 파일을 로드하도록 한다.
불러온 스크립트 텍스트는 eval()로 실행해 컨텐트 스크립트의 샌드박스에서 동작하도록 한다.

  // require_content.js
  require.load = function (context, moduleName, path) {
    var xhr = new XMLHttpRequest(),
      url = chrome.extension.getURL(path),
      nocache = '?' + (+new Date());
    
    xhr.open('GET', url + nocache, true);
    
    xhr.onreadystatechange = function (e) {
      if (xhr.readyState === 4 &&
          xhr.status === 200) {
        eval(xhr.responseText); // eval()로 컨텐트 스크립트의 샌드박스에서 실행하고,
        context.completeLoad(moduleName); // 스크립트가 로드되었음을 알린다.
      }
    };
    
    xhr.send(null);
  };


작성한 require_content.js 파일은,

아래와 같이 매니페스트 파일의 컨텐트 스크립트 부분에 추가해준다.

    {
      ...
      "content_scripts": [{
        ...
        "js": [
          "js/lib/require.js",
          "js/require_content.js",
          "js/require_config.js",
          "js/cscript/cscript_main.js"
        ],
        ...
      }
      ...
    }


eval()이 좀 찝찝하긴 하지만, 문제 없이 잘 동작한다.


자세한 내용은 아래를 참고한다.

- 크롬 익스텐션의 샌드박스: https://www.youtube.com/watch?v=laLudeUmXHM

- 샌드박스 문제에 대한 RequireJS 그룹스의 스레드:

      https://groups.google.com/forum/?fromgroups=#!topic/requirejs/elU_NYjunRw




#. 위 내용에 대해 아주 깔끔하게 정리한 프레지 프리젠테이션 문서를 찾았다.

http://prezi.com/rodnyr5awftr/requirejs-in-chrome-extensions/




카테고리

분류 전체보기 (707)
About me. (6)
Daylogs (672)
비공개 (0)
영어공부 (0)
My works - 추억 (29)