Skip to content

Commit

Permalink
docs: 크로스 브라우저 호환성 이슈 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
jaem1n207 authored and 이재민 committed Feb 15, 2024
1 parent 4da90c0 commit 17dc686
Showing 1 changed file with 109 additions and 11 deletions.
120 changes: 109 additions & 11 deletions Issue.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,112 @@
# 1번째 이슈: CSS 선택자 내 큰따옴표 이슈
Reflect, proxy, symbol, 가비지 컬렉션, globalThis polyfill, bundler, node

# 1번째 이슈: WebExtension API의 크로스 브라우저 호환성 문제 해결하기

## 배경

Chrome뿐만 아니라 다양한 브라우저를 지원하기 위한 작업을 시작했습니다. Chrome과 Firefox는 각각 `chrome``browser` 네임스페이스를 사용하여 API를 제공합니다.

이 두 브라우저 간의 주요 차이점 중 하나는 Chrome이 **콜백 기반**의 API를 제공하는 반면, Firefox는 **Promise 기반**의 API를 제공한다는 것입니다. 이로 인해 동일한 기능을 가진 확장 프로그램을 두 브라우저 모두에서 작동하게 만들기 위해서는 추가적인 작업이 필요했습니다.

## 문제 상황

Chrome에서 잘 작동했지만 Firefox에서는 `chrome.tabs.query``chrome.tabs.get`과 같은 API 호출이 문제를 일으키고 있었습니다. Chrome은 이러한 함수에 대해 콜백을 기대하지만, Firefox는 Promise를 반환합니다. 이로 인해 코드베이스에 크로스 브라우저 호환성 문제가 발생했습니다.

## 해결 과정

이 문제를 해결하기 위해, 각 브라우저에서 `chrome` 또는 `browser` 객체가 어떻게 존재하는지 파악했습니다. Chrome에서는 `browser` 객체가 존재하지 않았습니다. 반면에 Firefox에서는 `chrome`, `browser` 객체 모두 존재했습니다.

Firefox에서 `chrome.tabs.query` 함수를 호출하면 콜백을 통해 결과를 받을 수 있었고 `browser.tabs.query` 함수를 호출하면 Promise를 반환했습니다. Chrome에선 사용하는 Chrome 버전이 MV3를 지원할 경우에 콜백, Promise 어느 방식으로 하던 결과를 받을 수 있었죠. 기존 코드는 `chrome.tabs.query` API를 사용하며 Promise를 반환할 거라 예측하고 사용 중이었기에 Firefox에서는 탭 정보를 가져올 수 없었던 것입니다.

개인적으로 `Promise`를 사용하면 코드를 더 선언적으로 작성할 수 있으며, 코드의 가독성과 유지보수성이 높아지는 경험을 했기에 콜백보다 Promise를 더 선호합니다. 하지만 무작정 `Promise`를 반환하는 API를 사용한다면 구형 브라우저나 Manifest V3를 지원하지 않는 브라우저에서는 해당 기능을 사용할 수 없을 것입니다. 다양한 브라우저를 지원하는 목적이 더 많은 사용자가 이용하길 바래서인데 이 작업을 위해 구형 브라우저 사용자가 사용하지 못하게 되는 것은 제 목적과 맞지 않습니다. 그래서 다양한 브라우저에서 사용 가능하고 구형 브라우저 사용자도 챙기면서 코드의 가독성도 모두 챙기기 위한 작업을 진행했습니다.

브라우저 API 호출을 추상화하는 래퍼 함수를 만들어 해결할 수 있었습니다.

```typescript
// declaration type
interface Window {
webextension: typeof webExtension;
}

declare namespace webExtension {
namespace tabs {
type Tab = chrome.tabs.Tab | browser.tabs.Tab;
type QueryInfo = chrome.tabs.Tab | browser.tabs._QueryQueryInfo;
function query(queryInfo: QueryInfo): Promise<Tab[]>;
function get(tabId: number): Promise<Tab>;
}

namespace runtime {
function sendMessage(message: any, options?: browser.runtime._SendMessageOptions): Promise<any>;
}
}

// polyfill

// Promise를 반환하도록 합니다.
const wrapAsyncFunction = (method: FunctionShape) => {
return (target: typeof chrome | typeof browser, ...args: any[]) => {
return new Promise((resolve, reject) => {
method.call(target, ...args, (result: any) => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(result);
}
});
});
};
};

// 대상 객체의 기존 함수를 래핑하여 지정된 래퍼 함수에 의해 호출을 가로채도록 합니다. 래퍼 함수는 첫 번째 인자로 원래의 `target` 객체를 받은 다음, 원래 메서드에 전달된 각 인자를 받습니다.
const wrapMethod = (
target: typeof chrome | typeof browser,
method: any,
wrapper: FunctionShape
) => {
return new Proxy(method, {
apply(_, thisArg, args) {
return wrapper.call(thisArg, target, ...args);
}
});
};

// 함수를 가로채서 Proxy로 객체를 래핑합니다.
const wrapObject = (target: typeof chrome | typeof browser) => {
...
return new Proxy(proxyTarget, handlers) as typeof chrome;
};

const createWebExtensionPolyfillObj = (): typeof webExtension => {
if (!globalThis.chrome?.runtime?.id) {
throw new Error('This script should only be loaded in a browser extension.');
}

if (!globalThis.browser?.runtime?.id) {
return wrapObject(chrome) as typeof webExtension;
} else {
return globalThis.browser as typeof webExtension;
}
};

// using
const browser = createWebExtensionPolyfillObj();
return browser.tabs.query(queryInfo);
```

`browser` 객체가 존재하지 않는 브라우저(Chrome)에선 `chrome` 객체를 사용한다고 볼 수 있습니다. 이 함수는 콜백을 통해 결과를 제공하거나 `Promise`로 반환하는 `chrome.tabs.query` 함수를 Promise로 래핑합니다. 이 래퍼 함수를 이용하면 Firefox, Chrome에서 동일한 방식으로 사용할 수 있게 되며, 콜백을 통해 결과를 제공하는 구형 브라우저도 지원할 수 있게 됩니다. 여기에 더해 개발할 때 선언적인 코드를 작성할 수 있게 되는 이점도 챙기게 되었습니다.

## 결론

이러한 접근 방식을 통해, 확장 프로그램의 크로스 브라우저 호환성 문제를 성공적으로 해결할 수 있었습니다. 브라우저 간의 차이점을 걱정하지 않고, 확장 프로그램의 핵심 기능에 집중할 수 있습니다. 이 과정을 통해 브라우저 API의 차이점을 추상화하는 함수를 구현했고, 이는 향후 다른 API에도 적용될 수 있습니다.

### 참고

[JavaScript API에 대한 브라우저 지원](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs)
[webextension-polyfill](https://github.com/mozilla/webextension-polyfill/blob/master/src/browser-polyfill.js)
[Background scripts 디버깅](https://extensionworkshop.com/documentation/develop/debugging/#debugging-background-scripts)

# 2번째 이슈: CSS 선택자 내 큰따옴표 이슈

## 영상

Expand Down Expand Up @@ -63,16 +171,6 @@ https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector#paramete

> Note: Characters that are not part of standard CSS syntax must be escaped using a backslash character. Since JavaScript also uses backslash escaping, be especially careful when writing string literals using these characters. See Escaping special characters for more information.
# 4번째 이슈: WebExtension API를 위한 폴리필 이슈

## 배경

Chromium의 `chrome.tabs` API를 사용해 Tab 객체를 가져옵니다. 그러나 `Firefox`와 같은 브라우저에선 `chrome.tabs` 를 사용해 가져올 수 없습니다.

### 참고

[JavaScript API에 대한 브라우저 지원](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs)

# 5번째 이슈: 브라우저마다 다른 Manifest 설정

## 배경
Expand Down

0 comments on commit 17dc686

Please sign in to comment.