Bleeding edge

💭Floating Browser 개발기 본문

Side Project

💭Floating Browser 개발기

codevil 2022. 10. 30. 15:57

프로젝트를 시작하게 된 계기

처음 이 프로젝트를 시작하게 된 계기는

“윈도우에 있는 스티커 메모에 투명한 기능이 있었으면 좋겠다. “라고 생각하고 short-cut-marker 라는 이름으로 프로젝트를 시작하였다. 만들고 사용해 보니, 메모장 자체는 글자 크기가 고정되어 있어서 강조된 부분이 없어서 보기 불편했다. 또 메모장에 저장된 것은 핸드폰으로 확인할 수 없어서 아쉬웠다.

개선책 augmentedDevice

상단에 고정되고 투명도를 조절할 수 있는 브라우저를 만들고, 그 브라우저에서 노션이나 블로그를 이용하면 내가 원하는 메모에 하이라이트를 적용할 수 있고, 영상을 보면서 코딩할 일이 있을 때, 유튜브를 켜두고 작업하면 편할 것 같다는 생각이 들었다.

electron을 선택한 이유

프론트엔드 개발자인 나에게 있어 node.js는 상대적으로 진입장벽이 낮아서 선택하였다. 물론 인터프리터 방식을 사용하기에 디버깅이 쉽고, 빌드하기 쉽고 크로스 플렛폼을 지원한다는 다른 장점도 많다. 하지만 아무래도 진입장벽이 낮다는 것이 가장 큰 영향을 미쳤다

목표

  • 상단에 브라우저가 고정이 될 것
  • 주소창을 이용하여 입력한 주소값으로 이동할 수 있을 것
  • 앞으로 가기 뒤로 가기를 버튼으로 구현화할 것
  • input을 입력하여 투명도를 조절할 수 있을 것
  • 새 창 열기를 만들 것
  • 즐겨찾기를 구현할 것

문제점 그리고 해결책

주소창

정확한 주소를 입력하여 그 페이지로 이동하는 것은 너무나 간단했다. 하지만 문제는 정확한 주소를 입력할 때가 아닐 때가 문제였다. 프론트엔드 개발자의 단골 면접 브라우저에 주소 창에 naver.com를 치면 무슨 일이 생기나요? 라는 질문을 받은 것 같은 느낌이 들었다. 우선 주소창에 입력할 수 있는 경우가 어떤 경우가 있는지 생각해 보았다.

  1. 정확하게 주소를 입력한 경우
  2. http프로토콜을 제외한 경우
  3. port를 이용하여 접근한 경우
  4. 검색어를 검색한 경우

위의 네 가지 케이스에 맞게 정규식을 만들기로 하였다. 여기서 정규식을 deep을 하게 만드느냐를 고민하였다. 우선 3번 같은 경우 IPv4, IPv6와 localhost를 모두 체크하여 정규식을 짜는 방법이 있다. 디테일하게 짜는 것보다는 일반적으로 빠르게 케이스를 확인할 수 있게 간략화했다.

  1. “.”을 가지고 있는가

.을 이용하여 최상위 도메인(top level domain : .com 과 같은 것) 혹은 128.8에서의 .을 체크하기 위하여 조건에 넣었다.

  1. localhost를 가지고 있는가
  2. 위의 두 가지 다 해당하지 않으면, 구글 검색창에 검색하는 것처럼 string을 붙여서 값을 이용한다
"<http://www.google.com/search?q=>" + something
  1. 1번 2번 둘 중 하나라도 해당하는 경우, http를 포함하고 있다면 값을 그대로 사용한다
  2. 없다면 http를 붙인다.
if (code === "Enter") {
      $webview.loadURL(
        !target.value.includes(".") && !target.value.includes("localhost")
          ? "<http://www.google.com/search?q=>" + target.value
          : target.value.includes("http")
          ? target.value
          : "http://" + target.value
      );
    }

https가 존재하는 웹 같은 경우 http를 입력해도 https로 이동하기 때문에 과감하게 https로 리다이렉트이 되기 때문에 특별하게 수정하지 않았다.


새 창 문제

새 창을 누르고 창을 종료했을 때, 모든 창이 닫히거나, 에러가 뜨는 문제가 있었다. 처음에는 버튼을 누르고 window.close()로 처리를 하였다. 그러나, 최대창(maximize)를 구현하면서, 다시금 문제가 생겼다.

원인

document 하나만 신경 쓰는 자바스크립트와 다르게 electron은 하나의 메인에 여러 가지의 window를 실행한다. 지금 새 창문제가 생긴 이유는 화면에서 무언가 닫기를 하거나 최대창을 적용한다면, 그 창을 알맞게 지정을 하지 않았았기 때문에 문제가 생겼다.

창이 한 개라면 상관없겠지만, 창을 여러 개 띄운다면 한 개의 메인에 여러 개의 렌더 프로세스가 연결되기에 이 값을 구분 해 줘야 한다.

remote

remote getCurrentWindow를 이용하여 window를 얻으라는 이야기가 많았다. 하지만 remote는 deprecate가 되었기 때문에 과감하게 버리기로 하였다. 해결을 하기 위해 window를 구분할 수 있는 unique 한 것이 있는지 검색해 봤다

const windows = {};

const createWindow = () => {
  let mainWindow = new BrowserWindow({
    width: 600,
    height: 600,
    transparent: true,
    frame: false,
    icon: __dirname + "/assets/logo.ico",
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false,
      webviewTag: true,
      enableRemoteModule: true,
    },
  });
  mainWindow.loadFile("index.html");
  mainWindow.setAlwaysOnTop(true, "floating");
  windows[mainWindow.id] = mainWindow;
};

BrowserWindow를 인스턴스로 생성하였을 때 프로퍼티로 id 값을 가지고 있었다.

ipcMain.on("full-browser", ({ sender }) => {
  const id = sender.getOwnerBrowserWindow().id;
  if (windows[id].isMaximized()) windows[id].unmaximize();
  else windows[id].maximize();
});
ipcMain.on("close-browser", ({ sender }) => {
  const id = sender.getOwnerBrowserWindow().id;
  windows[id].close();
  delete windows[id];
});

최대창 혹은 창이 닫힐 때, 그 창을 알맞게 지정하여서 처리를 하도록 만들었다.

기준점

새창 문제를 해결하면서 electron을 개발할 때 새로운 고민이 하나 생겼다. 어떤 것을 ipcMain에서 처리하고 어떤 것을 ipcRenderer에서 처리할까? 나중에 헷갈리지 않기 위해 이번 프로젝트에 맞는 케이스별로 분류를 해봤다.

  • 렌더러에서만 사용되는 이벤트 : ipcRenderer 이용 없이 renderer에서 처리
  • 렌더러 창에 영향을 주는 이벤트 : ipcRenderer.send ⇒ ipcMain.on
  • 렌더러 전체에 영향을 주는 이벤트 : ipcMain.send⇒ipcRenderer.on

즐겨찾기

가장 고생했었던 파트이다.  즐겨찾기는 안 하고 끝내려고 했었는데, 커넥투 강사님이 즐겨찾기 추가해 보는 건 어때요? 라는 제안을 듣고 추가하기로 결정했다.

즐겨찾기는 어디에 저장할까?

로컬 스토리지 JSON 두 가지 중에서 고민을 먼저 했었다. JSON을 선택한 이유는 우선 JSON으로 저장하면 값을 옮기기 쉽고, 브라우저 특성상 로컬 스토리지를 지우는 경우가 있는데 삭제되지 않는 값과 분류하고 싶어서 JSON을 선택하였다.

즐겨찾기는 어떤 프로세스로 이루어질까?

이 브라우저를 만들기 전에는 즐겨찾기 별 버튼을 누르면 창이 뜨고, 확인을 누르면 즐겨찾기가 추가되고 끝날 줄 알았다. 근데 브라우저를 만들려고 고민을 하다 보니, 생각보다 번거로운 작업이었다.

처음 렌더

  1. 브라우저를 키면 첫 주소가 즐겨찾기에 저장되어 있는지 확인한다
  2. 즐겨찾기에 없는 주소라면 그대로, 즐겨찾기에 있는 주소라면 이 색칠된다

즐겨찾기에 없는 주소에서의 클릭

  1. JSON에 현재 주소가 추가된다. (ipcRenderer⇒ipcMain)
  2. 이 색칠된다 (☆=>★)
  3. 모든 브라우저에 즐겨찾기 버튼이 생성된다.(ipcMain ⇒ipcRenderer)
  4. 즐겨찾기 이름을 바꿀 수 있는 창이 뜬다. ⇒이름 업데이트는 1번, 3번을 반복 수행한다.(현재 주소가 업데이트되고, 즐겨찾기 버튼이 업데이트된다)

즐겨찾기에 있는 주소에서의 ★ 클릭

  1. 즐겨찾기 이름을 바꿀 수 있는 창이 뜬다. (이름을 바꾸는 버튼을 누르면 위의 4번과 동일한 과정을 수행)
  2. 삭제 버튼을 누른다
  3. JSON에서 즐겨찾기 주소를 삭제한다. (ipcRenderer⇒ipcMain)
  4. ★의 속이 빈다. ( => ☆)
  5. 모든 브라우저에 즐겨찾기 버튼이 사라진다.(ipcMain ⇒ipcRenderer)

다음과 같이 진행 단계를 나누니까 랜더의 순서를 짜는 데 있어서는 문제가 없었지만, 생각과 다르게 즐겨찾기를 적용한 페이지만 맞게 움직이고 있었다. 문제는 JSON 파일을 불러들이는 시점에 값이 모두 본인이 추가되었을 때를 기준으로 하고 있기 때문이었다. 그래서 JSON의 값이 바뀌는 시점에서는 모두 다 JSON을 확인하도록 만들었더니 문제가 해결되었다


개발 후기

프론트엔드 개발자로써 브라우저를 만들면서, 브라우저가 웹을 그리기 전에 어떤 행동을 하는지 그리고 브라우저가 어떤 라이프 사이클을 가지고 있는지 공부할 수 있었다. 브라우저를 만들다 보니, UI/UX가 브라우저 자체를 고려해서 UI UX를 만드는 것도 배울 수 있었다. chromium 을 이용하여 만들기 때문에 크로스 브라우징 이슈가 없을 줄 알고 좋아하고 있었지만, 빌드를 하면서 iOS와 Window, 다른 크로스 디바이스들을 보고서 프론트엔드에게 십자가(크로스)를 짊어지는 것은 어쩔 수 없는 것 같다.

아쉬운 점

브라우저에 정말 많은 기능이 있었는데, 그 중에서 마음에 드는 기능을 가져오지 않아서 아쉬웠다. 틈나면 조금 더 만들어서 업데이트를 해야겠다. 이번에 git actions를 이용하여 자동 배포를 시도했었는데 실패했다. git actions를 조금 더 공부하여 배포를 다시 해봐야겠다. 

마무리

개발을 시작한 2월부터 10월까지 제로베이스 커넥투 과정을 듣는 동안 항상 사이드 프로젝트를 했었지만, 이번 달에 만든 백준 애드온과 플로팅 브라우저는 다른 사람들도 같이 이용하고 피드백을 줘서 재밌게 만들었던 것 같다. 이번 달에 야무님의 강의를 들으면서 공식 문서에 중요성에 대해 배우고 이번 electron에 많이 집중을 했었는데, electron은 예시가 적어서 좀 아쉬웠지만, 공식 문서에서 deprecate된 것들을 빠르게 캐치해서 불필요한 시간을 줄일 수 있어서 좋았다.