# Chrome Extension 개발

## 🔎  Chrome Extension을 개발하기 전..

크롬 익스텐션!! 요것만 알면 시작할 수 있따! 하는 것들을 간략하게 정리해보도록 하겠습니다. 🙂

* 크롬 익스텐션에서 사용되는 스크립트와 이벤트
* manifest.json 파일 살펴보기
* 디버깅은 어떻게 하는가?

### 1) 크롬 익스텐션에서 사용되는 스크립트와 이벤트

크롬 익스텐션을 개발하는데 사용되는 스크립트는 크게 `background`, `contentscript`, `popup` 이 있습니다. ![image](https://user-images.githubusercontent.com/25981942/51577997-2fdcbd00-1eff-11e9-8700-40e487ea9307.png)

`background`는 크롬 익스텐션이 실제로 동작하는데 필요한 스크립트이고, `contentscript`는 사용자 화면의 DOM, 스크립트들을 제어하는 데 필요한 스크립트 입니다. `popup`은 크롬 익스텐션 버튼을 눌렀을 때 아래에 뜨는 팝업창을 위한 스크립트 입니다. 팝업창의 모양을 그리기 위해서 html 파일도 있어야겠져?!! 그래서 `popup.html`도 존재합니다..

크롬에서는 크롬 익스텐션의 `background` 스크립트를 디버깅 하기 위해서 [별도의 개발자 도구 창](https://developer.chrome.com/extensions/getstarted#background)을 제공하는데요, `background` 스크립트의 console.log는 이 개발자 도구에 기록되고, `contentscript` 스크립트는 크롬 익스텐션을 실행 중인 사용자의 화면의 개발자 도구 창에 기록이 남습니다. ~~나의 로그가.. 왜 안나타나는가 헤맬까봐...\*~~

그리고 storage에 저장하거나.. 하는 등의 액션은 모두 `background` 스크립트에서만 동작 가능하기 때문에, `contentscript`에서는 이벤트를 발생시켜서 이런 액션들을 처리해주어야 합니다.

* `contentscript`에서 이벤트 발생 후 `background` 에서 listening 하기

```javascript
// contentscript.js
chrome.runtime.sendMessage({action: "FINISH"}, function(response) {
    alert(response);
});
```

```javascript
// background.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    console.log(sender.tab ?
        "from a content script:" + sender.tab.url :
        "from the extension");

    if (request.action === "FINISH")
        sendResponse({farewell: "goodbye"});
});
```

> `chrome.runtime` 의 내부 메서드인 `sendMessage`를 사용해서 이벤트를 보내고, `addListener`를 통해서 이벤트를 듣고 있도록 되어 있습니다.

* `popup` 에서 클릭 했을 때 사용자 화면을 바꾸도록 하려면?

```markup
<!--popup.html-->
<button id="start">분석 시작</button>
<script type="text/javascript" src="scripts/popup.js"></script>
```

```javascript
// popup.js
document.getElementById("start").onclick = function () {
    chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {action: "START"}, /* callback */);
    });
};
```

```javascript
// contentscript.js
chrome.extension.onMessage.addListener(function (msg, sender, sendResponse) {
    if (msg.action === "START") { /* do something */ }
});
```

> 런타임 환경이 아닌 익스텐션에서 메시지를 주고 받을 때는 `chrome.extension.onMessage.addListener` 를 사용합니다. ⚠️ 그리고, `popup.js`에서 특정 탭에 이벤트를 보내기 위해서 tab의 id를 꼭 포함해서 보내주어야 합니다.

### 2) [manifest.json 파일](https://developer.chrome.com/extensions/extensions/manifest)

크롬 익스텐션의 설정파일들을 담아주는 곳이고, 아래와 같이 간-단한 정보들을 담고 있습니다.

```javascript
{
    "name": "Getting Started Example",
    "version": "1.0",
    "description": "Build an Extension!",
    "manifest_version": 2
}
```

그리고 크롬익스텐션에서 사용할 스크립트도 등록해주어야 합니다.

* `background` 스크립트 등록

```diff
{
    "name": "Getting Started Example",
    "version": "1.0",
    "description": "Build an Extension!",
+   "background": {
+     "scripts": ["background.js"],
+     "persistent": false
+   },
    "manifest_version": 2
}
```

> ⚠️ `persistent` 옵션은 `false`가 기본이지만, 사용자의 화면에서 발생하는 request들을 핸들링 하기위해 `webRequest`를 사용할 때는 `true`로 해주어야 정상 동작합니다.

* `contentscript` 등록

```diff
{
    "name": "Getting Started Example",
    "version": "1.0",
    "description": "Build an Extension!",
    "background": {
      "scripts": ["background.js"],
      "persistent": false
    },
+   "content_scripts": [
+     {
+       "matches": ["http://*.nytimes.com/*"],
+       "css": ["myStyles.css"],
+       "js": ["contentScript.js"]
+     }
+   ],
    "manifest_version": 2
}
```

* `popup` 등록

```diff
{
    "name": "Getting Started Example",
    "version": "1.0",
    "description": "Build an Extension!",
+   "page_action": {
+     "default_icon": "icons/32.png",
+     "default_title": "Extension",
+     "default_popup": "popup.html"
+   },
    "background": {
      "scripts": ["background.js"],
      "persistent": false
    },
   "content_scripts": [
     {
       "matches": ["http://*.nytimes.com/*"],
       "css": ["myStyles.css"],
       "js": ["contentScript.js"]
     }
   ],
    "manifest_version": 2
}
```

```markup
<!--popup.html-->
<button id="start">분석 시작</button>
<script type="text/javascript" src="scripts/popup.js"></script>
```

> ⚠️ `popup` 스크립트는 manifest에 등록하는 것이 아니라, `popup.html`에 script tag로 삽입해주면 됩니다.

* Permission 등록하기

```diff
{
    "name": "Getting Started Example",
    "version": "1.0",
    "description": "Build an Extension!",
    "manifest_version": 2,
+   "permissions": [
+     "tabs",
+     "declarativeContent"
+   ]
}
```

> 권한 종류는 [요기](https://developer.chrome.com/apps/declare_permissions) 가면 더 볼 수 있습니다.

### 3) 디버깅은 어떻게 하는가?

디버깅은 짱! 간단합니다.. 주소창에 chrome://extensions/ 를 입력하시고..

아래와 같이 개발중인 폴더째로 로드하고.. 성공하면 백그라운드 페이지를 누르면 디버깅 할 수 있는 별도의 개발자도구 창이 뜹니다.. 끗...

![image](https://user-images.githubusercontent.com/25981942/51578211-1b4cf480-1f00-11e9-978d-ff51fdea7919.png)

### 🐝  특정 도메인에서만 툴팁이 노출되었으면 좋겠어요!

> manifest permission : `"declarativeContent"` 구현 스크립트 : background.js

```javascript
chrome.runtime.onInstalled.addListener(function () {
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function () {
        chrome.declarativeContent.onPageChanged.addRules([{
            conditions: [new chrome.declarativeContent.PageStateMatcher({
                pageUrl: {hostSuffix: 'abc.com'},
            })],
            actions: [new chrome.declarativeContent.ShowPageAction()]
        }]);
    });
});
```

* 런타임 `onInstalled` 이벤트가 발생하였을 때, 이런 조건(`conditions`)일 때 이런 액션(`actions`)를 해라! 라는 코드인데요!
  * 조건 종류는 [여기](https://developer.chrome.com/extensions/declarativeContent)에서 더 확인할 수 있습니다.
* `hostSuffix` 즉, host가 'abc.com' 으로 끝나는 경우 `ShowPageAction()` 을 하라는 것입니다. manifest 에.. popup.html을 `page_action` 에 지정했던 것 기억하시나요?! 그래서 `ShowPageAction()`을 호출하면 popup을 노출 시킬 수 있습니다. \~왠지.. `ShowPopup()` 이여야할 것 같은데...\*\~
* 그렇다면, `abc.com`이 아닌 도메인에서는 어떻게 노출되는지 궁금하시죠? ![image](https://user-images.githubusercontent.com/25981942/51578302-6830cb00-1f00-11e9-923c-3becdd2ed931.png)
* 조건이 일치하는 경우에는 팝업이 뜹니다..

## 기타

### 🐝 사용자 화면의 Http 요청을 제어할 수 있을까요?

> manifest permission : `"webRequest"` "persistent" : true
>
> ```diff
> // manifest.json
> {
>   "background" : {
>       "scripts" : ["background.js"],
> +     "persistent" : true
>   }
> }
> ```
>
> 구현 스크립트 : background.js

* 네!! 제어할 수 있습니다.

  자세한 설정은 [chrome.webRequest](https://developer.chrome.com/extensions/webRequest) 에 있습니다.

#### `onBeforeRequest` 예시

```javascript
chrome.webRequest.onBeforeRequest.addListener(
    function(details) {
        console.log(details.url.match(/a=(.*?)&/i)[1]);
                // return ~~ ;
    },
    {
        urls: [
            "*://abc.com/*",
            "*://beta-abc.com/*",
            "*://alpha-abc.com/*"
        ]
    }
);
```

* 위 코드는 http 요청 전의 `onBeforeRequest` hook을 사용한 것입니다.
* http 요청 전 urls에 해당하는 것들만 걸러서, 요청에 대한 정보를 콜백의 인자(`details`)로 넘겨줍니다.

#### 🚫 요청 cancel 예시

```javascript
chrome.webRequest.onBeforeRequest.addListener(
    function(details) {
        return {cancel: details.url.indexOf("://www.evil.com/") != -1};
    },
    {urls: ["<all_urls>"]},
    ["blocking"]
);
```

* 뿐만 아니라 이 콜백에서 `return {}` 시 여러 옵션을 넘겨서 요청을 수정하거나 취소하거나 하는 등의 제어도 가능합니다.
* 콜백의 리턴 값으로 `cancel : boolean` 프로퍼티를 이용해서 request를 취소 할 수도 있습니다.
* ⚠️이렇게 request를 취소하는 경우에는 `"webRequest"` Permission 뿐만아니라 `"webRequestBlocking"`도 추가해주어야 합니다.
* 그러나 아래와 같이 custom 한 스크립트를 동작시키고 싶을 때 원하는 동작이 실행되지 않을 수도 있습니다.

```javascript
window.__myString = "퇴근하자";
```

이렇게 대입을 해도 클라이언트 사이드에서는 적용이 안됩니다. 😓

* 이런 경우에는 `script` 태그를 만들어서 DOM에 append 해주는 방식으로 처리해야 합니다.

```javascript
const clickcrScriptEl = document.createElement("script");
clickcrScriptEl.innerHTML = `window.__myString = "퇴근하자";`;
document.body.appendChild(clickcrScriptEl);
```

* 하지만!! 이렇게 모두 script를 string으로 대입하는 것은.. 굉장히 번거로운 작업이져!!

```javascript
var fullPath = chrome.extension.getURL('scripts/abc.js');

const script = document.createElement('script');
script.type = 'text/javascript';
script.src = fullPath;
document.body.appendChild(script);
```

> `chrome.extension.getURL`을 사용하면 chrome extension 내 스크립트 환경에 있는 script 를 fullpath로 가져올 수 있습니다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://tidyline.gitbook.io/today-i-learned/etc/chromeextension.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
