web/etc

크롬 익스텐션 Content Scripts 번역

qkqhxla1 2017. 2. 23. 22:02

원문 : https://developer.chrome.com/extensions/content_scripts


Content Scripts는 웹 페이지의 안에서 돌아가는 자바스크립트 파일입니다. 표준 DOM을 사용해서 브라우저가 방문한 웹 페이지의 정보를 읽거나, 변화를 만들어 낼 수 있습니다.


아래에 content scripts가 할수 있는 것들이 있습니다.


1. 하이퍼링크로 처리되지 않은 url들을 하이퍼링크로 만들기.

2. 읽기 쉽게 폰트 사이즈 키우기.

3. DOM의 microformat data 찾고 변경하기.


content scripts는 몇가지 제한이 있습니다. 아래는 content scripts가 할수 없는 것들입니다.


1. 아래를 제외한 크롬 api사용하기.

사용할수 있는 것들.

2. 익스텐션의 페이지에 정의된 변수나 함수 사용하기.

3. 웹 페이지나 다른 content scripts에 있는 변수나 함수 사용하기.


이러한 제한사항들은 그리 나쁘지는 않은 것 같습니다. content scripts는 간접적으로 크롬의 api를 사용하고, 익스텐션의 데이터를 얻어오고, 그들의 부모 익스텐션과 메시지를 교환함으로서 익스텐션의 액션을 요청합니다. 또한 content scripts가 부모 사이트로서

cross-site XMLHttpRequests를 요청할수 있고, 공유된 DOM을 사용해서 웹 페이지와 통신을 할 수 있습니다. content script가 뭘 더 할수 있는지와 없는지를 알려면 실행 환경을 참조합니다.. 


manifest

content script가 페이지에 항상 삽입되어 있어야 한다면 익스텐션의 manifest파일에 아래처럼 등록하면 됩니다.

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["http://www.google.com/*"],
      "css": ["mystyles.css"],
      "js": ["jquery.js", "myscript.js"]
    }
  ],
  ...
}

일부 시기에만 스크립트를 삽입하고 싶으면 Programmatic injection에 기술된 permissions 필드를 이용합니다. 

{
  "name": "My extension",
  ...
  "permissions": [
    "tabs", "http://www.google.com/*"
  ],
  ...
}

content_scripts 필드를 사용해서 페이지에 여러개의 content script를 삽입할수 있습니다. 각각의 content script는 여러개의 자바스크립트와 css파일을 가질 수 있습니다. 각각은 아래의 property를 가집니다. 

이름

타입 

설명 

matches

 array of strings

반드시 필요합니다. 현재 content script가 어느 페이지에 삽입될지 명세합니다. 더 자세한 문법을 알고 싶으면 Match Patterns 를 보고, 어떻게 몇몇 url을 제외시킬건지 확인하려면 Match patterns and globs를 보면 됩니다.. 

exclude_matches

 array of strings 

옵션. 현재 content script가 삽입되지 않을 페이지. 바로 위의 Match Patterns로 들어가서 문법을 더 확인하거나, 바로 위의 Match patterns and globs로 가서 어떻게 제외시킬지 보면 됩니다.. 

match_about_blank

 boolean

옵션. 현재 content script가 about:blank페이지나 about:srcdoc페이지에 

삽입될지 안될지를 결정합니다. content script는 inherit url이 matches필드에서 규칙에 맞을 경우에 페이지에만 삽입됩니다. inherit url은 frame이나 window에 의해 생성되는 url을 말합니다. 하지만 sandboxed된 frame에는 삽입되지 않습니다. false를 디폴트값으로 가집니다.

css

 array of strings

옵션. 매칭된 페이지에 삽입될 css 파일의 목록입니다. 리스트 안에 있는 순서대로 페이지에서 DOM이 만들어지거나 보여지기 전에 삽입됩니다.

js

 array of strings

옵션. 매칭된 페이지에 삽입될 자바스크립트 파일의 목록입니다. 리스트 안에 있는 순서대로 삽입됩니다. 

run_at

 string

옵션. js파일이 삽입되었을때 컨트롤합니다. document_start, document_end, document_idle 일 수 있습니다. 기본값으로 document_idle를 가집니다.


document_start일 경우에는 css파일이 삽입되고 난 후이지만 DOM이 구성되기 전이며 다른 스크립트가 실행되기 전에 삽입됩니다.


document_end일 경우에는 DOM이 구성되고 난 후이지만 images나 frames가 로딩되기 전에 삽입됩니다. 


document_idle의 경우에는 브라우저가 'document_end'과 'window.onload 이벤트 파일이 생성된 후', 둘중에 한가지를 골라서 삽입합니다. 정확히 언제 삽입될지는 document가 얼마나 복잡한지, 로딩되는지 얼마나 걸리는지, 페이지가 로딩되는데 최적화에 달려있습니다.


주목 : document_idle일때는 content scripts가 window.onload이벤트가 끝난 후에 실행될 수도 있어서 window.onload이벤트를 받을 필요가 없을수도 있습니다. 대부분의 경우에서 document_idle일 경우에 onload이벤트를 기다리는것은 DOM이 끝나고 난 후에 실행되는 것이기 때문에 필요가 없습니다. 진짜 확실하게 window.onload가 끝난 후에 실행시키고 있으면 document.readyState 프로퍼티를 사용해서 체크할수 있습니다.

all_frames

 boolean 

옵션. 모든 content script가 매칭된 모든 frame안에서 돌아가는지, 아니면 

top에서만 돌아가는지 컨트롤합니다. (top이란 브라우저의 본 페이지? 를 말합니다.)

include_globs 

 array of string

옵션. 현재 glob 필드와도 매칭되는지 확인하기 위해 matches필드가 적용된 후에 실행됩니다. Greasemonkey keyword인 @include를 조작하기 위해 사용되었습니다. 더 많은 정보를 보려면 여기를 참조합니다.

exclude_globs 

 array of string

옵션. 현재 glob필드와 매칭되는지 확인하기 위해 matches필드가 url을 제외한 후에 실행됩니다. Greasemonkey keyword인 @exclude를 조작하기 위해 사용됩니다. 더 많은 정보를 보려면 여기를 참조합니다.


매칭 패턴과 globs.


페이지의 url이 matches패턴이나 include_globs패턴과 맞고, exclude_matches와 exclude_globs패턴과 맞지 않으면 content script가 삽입됩니다. matches패턴이 반드시 필요한 패턴이기 때문에 exclude_matches, include_globs, exclude_globs는 페이지들을 더 세부적으로 나누기 위해서만 사용됩니다.


예를 들어 matches패턴이 ["http://*.nytimes.com/*"]고 가정해봅시다.


만약 exclude_matches가 ["*://*/*business*"]이면 content script는 http://www.nytimes.com/health에는 삽입될테지만, 

http://www.nytimes.com/business에는 삽입되지 않을 것입니다.


또 include_globs가 ["*nytimes.com/???s/*"]이면 content script는 http://www.nytimes.com/arts/index.html와 http://www.nytimes.com/jobs/index.html에는 삽입될거지만, http://www.nytimes.com/sports/index.html에는 삽입되지 않을 것입니다.(중간에 ???s는 정규식인것 같네요. 4글자이면서 s로 끝나는 것.)


exclude_globs가 ["*science*"]라면 content script는 http://www.nytimes.com에는 삽입될거지만 http://science.nytimes.com이나 http://www.nytimes.com/science에는 삽입되지 않을 것입니다.


globs 프로퍼티는 match 패턴보다 더 유연한 문법을 가집니다. 적용 가능한 glob문자열은 *나 ?같은 와일드카드 문자열을 포함하는 url입니다. *는 어떤 길이의 문자열과도 매칭됩니다.(공백 포함) ?는 한글자의 문자형과 매칭됩니다.(정규식과 같네요.)


예를 들어 http://???.example.com/foo/*는 아래의 주소들과 매칭됩니다.

http://www.example.com/foo/bar

http://the.example.com/foo


하지만 아래의 주소들과는 매칭되지 않습니다.

http://my.example.com/foo/bar

http://example.com/foo/

http://www.example.com/foo


프로그래밍적인 삽입.

코드를 프로그래밍적으로 삽입하는건 개발자의 자바스크립트나 css의 코드가 matches패턴에 일치하는 어떤 페이지에도 삽입되지 않을 때 유용합니다. 예를 들어 유저가 브라우저의 action 아이콘을 클릭했을때만 스크립트를 실행시키고 싶다고 가정해봅시다.


코드를 페이지에 삽입하기 위해서 익스텐션은 cross-origin permission권한을 가져야 합니다. 그리고 chrome.tabs모듈을 사용해야 합니다. manifest파일의 permission필드를 사용하면 두가지 권한을 줄 수 있습니다. 


권한이 설정되면 tabs.executeScript를 사용해서 자바스크립트를 페이지에 삽입할 수 있습니다. css를 삽입하려면 tabs.insertCSS를 사용하세요.


make_page_red익스텐션에서 가져온 아래의 코드는 사용자의 클릭에 반응해서 자바스크립트를 현재의 페이지에 삽입하고 실행시킵니다.

chrome.browserAction.onClicked.addListener(function(tab) {
  chrome.tabs.executeScript({
    code: 'document.body.style.backgroundColor="red"'
  });
});
"permissions": [
  "activeTab"
],

브라우저가 http 페이지를 보여주고 유저가 익스텐션을 클릭하면 익스텐션이 페이지의 bgcolor 프로퍼티를 red로 설정합니다. 결과적으로 해당 페이지가 바탕 화면을 설정하는 css를 가지고 있지 않는한 페이지의 배경색이 빨간색으로 바뀝니다.


하지만 대부분 코드를 직접 삽입하기보다는 코드를 파일에 넣습니다. 그리고 해당 파일을 아래처럼 삽입합니다.

chrome.tabs.executeScript(null, {file: "content_script.js"});

실행 환경.

content script는 고립된 환경에서 실행됩니다. content script가 삽입된 페이지의 DOM에는 접근할 수 있지만, 페이지의 자바스크립트 변수나 함수에는 접근할 수 없습니다. 이렇기 때문에 content script의 입장에서는 돌아가는 다른 자바스크립트가 없는 것처럼 보입니다. 이것의 대우도 참입니다. 페이지에서 돌아가는 자바스크립트는 content scripts의 어떤 함수나 변수도 호출할 수 없습니다. 


예를 들면 아래와 같은 간단한 페이지가 있다고 가정해봅시다.

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener("click", function() {
      alert(greeting + button.person_name + ".");
    }, false);
  </script>
</html>

그리고 아래의 content script가 위의 페이지에 삽입되었다고 가정해봅시다.

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener("click", function() {
  alert(greeting + button.person_name + ".");
}, false);

버튼이 눌리면 두개의 greeting를 볼수 있습니다. (실행환경이 독립되었기 때문에 각각 실행됨.)


고립된 실행환경은 현재 페이지에서 실행되는 자바스크립트와 content script가 서로 충돌날 걱정 없이 작용하는것을 보장합니다. (위의 예처럼 같은 변수를 써도 충돌나지않고 각각 돌아감.) 예를 들면 content script는 JQuery v1을 포함시킬 수 있지만 페이지의 자바스크립트는 JQuery v2를 포함시킬 수 있고, 서로 충돌나지 않습니다.


고립된 실행환경의 중요한 또다른 장점은 content script의 자바스크립트와 페이지의 자바스크립트를 완전히 분리한다는 것입니다. 이것을 이용해서 content script에 추가 기능을 넣을 수도 있습니다.


페이지와 익스텐션에서 공유되는 자바스크립트 객체에서 어떤 일이 생기는지 알아두는게 좋습니다. 예를 들어 window.onload이벤트입니다. 각각의 고립된 실행환경에서 자기 자신의 객체만 봅니다. 예를 들어 페이지와 익스텐션 둘다 window.onload를 할당할 수 있지만 서로의 이벤트 핸들러를 읽을수 없습니다.(실행환경의 독립 때문에..) 해당 이벤트 핸들러는 자기 자신의 실행환경에서만 할당되고 볼수 있습니다.


공유 페이지를 통한 통신.

content script와 페이지가 실행환경이 서로 독립되어 있지만 공유된 DOM으로 통신이 가능합니다. 페이지가 content script와 통신하고싶으면(또는 익스텐션이 content script를 통해서 통신하고싶으면) 공유된 DOM을 사용해서 통신이 가능합니다.


아래는 window.postMessage를 사용해서 통신한 예제입니다.

var port = chrome.runtime.connect();

window.addEventListener("message", function(event) {
  // We only accept messages from ourselves
  if (event.source != window)
    return;

  if (event.data.type && (event.data.type == "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);
document.getElementById("theButton").addEventListener("click",
    function() {
  window.postMessage({ type: "FROM_PAGE", text: "Hello from the webpage!" }, "*");
}, false);

위의 예에서 익스텐션의 일부가 아닌 아래 소스는 content script에서 가져갈수 있는 메시지를 본인에게 보냅니다. 이런 방법을 사용해서 페이지와 익스텐션이 서로 통신이 가능합니다. 반대의 경우도 비슷한 방법을 사용해서 가능합니다.


보안에 대해서.

content script를 작성할때 두가지의 보안 이슈를 고려해야 합니다. 첫번째로 content script에 삽입되는 보안취약점을 웹 페이지에 가져가면 안 된다는 겁니다. 예를 들어서 개발자가 개발한 content script가 다른 웹사이트로부터 XMLHttpRequest등을 사용해서 메시지를 받는다면 페이지에 삽입하기 전에 XSS공격에 대해서 주의해야 합니다. 예로 innerHTML대신 innerText를 사용해서 스크립트를 삽입하는것을 추천드립니다. 또 http content를 가져올때 mitm공격이 올 수 있으므로 주의해야 합니다.


두번째로 실행 환경이 독립되어 있다지만 content script가 무차별적으로 웹 페이지의 콘텐츠를 가져온다면 악의적인 웹 페이지는 개발자의 content script를 공격할수 있습니다. 예를 들어 아래의 패턴이 있습니다.

var data = document.getElementById("json-data")
// WARNING! Might be evaluating an evil script!
var parsed = eval("(" + data + ")")
var elmt_id = ...
// WARNING! elmt_id might be "); ... evil script ... //"!
window.setTimeout("animate(" + elmt_id + ")", 200);

대신에 더 안전한 api를 사용하길 권장합니다. 

var data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
var parsed = JSON.parse(data);
var elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(function() {
  animate(elmt_id);
}, 200);

익스텐션 파일 가져오기.

chrome.extension.getURL()을 사용해서 익스텐션 파일의 url을 가져올수 있습니다. 아래의 코드를 보면서 확인하세요.

//Code for displaying <extensionDir>/images/myimage.png:
var imgURL = chrome.extension.getURL("images/myimage.png");
document.getElementById("someImage").src = imgURL;

예제. content script를 사용한 많은 예제를 찾을수 있을 것입니다. 메시지를 통한 통신의 예제는 Message Timer가 있습니다. Page RedderEmail This Page를 보면서 프로그래밍적인 삽입 예제를 볼 수 있습니다. 


비디오가 더 있는데 그건 원본 링크를 참조해서 봅시다. 영어이지만 이해하기 매우 좋네요.

'web > etc' 카테고리의 다른 글

크롬 익스텐션 Autoupdating 번역.  (0) 2017.02.28
크롬 익스텐션 Overview 번역  (0) 2017.02.20
웹 브라우저 동작 원리  (0) 2017.02.17
크롬 익스텐션 Getting Started Tutorial 번역  (1) 2017.01.31