본문 바로가기
Project/스타게이트

Firefox에서의 setInterval 문제 해결

by 이의찬 2024. 2. 7.

0.TL;DR

  • 타이머를 setinterval을 사용해 구현
  • V8엔진을 사용하는 크롬/엣지 등에서는 문제가 없지만 Firefox에서는 오차 누적 이슈 발견
  • userAgent를 사용해 접속환경에 따라 경고문을 출력하도록 함
  • 또한 Firefox라면 delay값을 이전 오차값에 따라 적절하게 변동해서 넘겨주도록 구현

1. Why?

<스타게이트>에서는 setInterval 문제가 다 해결된줄 알았다. 그런데 "크롬에서는 setInterval  보정이 들어가지만, firefox에서는 보정이 들어가지 않는다"는 글을 보게 되었다.

아오 파이어폭스시치!!

브라우저별로 실행 엔진이 다르니까 충분히 가능한 이야기인데 어째서 고려를 안했을까...🥲 혹시나 해서 chrome과 같은 V8 엔진을 사용하는 edge와 brave 브라우저에서도 실행해봤는데, 얘네는 보정이 들어가고 있었다.


2. 왜 이런 문제가 발생하는가

JavaScript는 해석하고 실행시키는 엔진과 실행환경(런타임)에 따라 미묘하게 다르게 동작하는 경우가 있다. 물론 ECMAScript표준을 지켜야하기에 거의 대부분은 동일하게 동작하지만, 불운하게도 setInterval의 보정은 그 "거의 대부분"에 들어가지 않는다. Js 엔진과 런타임, 그리고 비동기 처리 등에 대해서는 설명하려면 꽤 복잡한 개념이기에 여기서는 간략하게 설명하겠다.

  • JavaScript 엔진 : JavaScript 코드를 해석하고 실행하는 인터프리터
    • V8 : Edge, Chrome, Node.js에서 사용
    • SpiderMonkey : Firefox에서 사용
    • JavaScript Core : Safari에서 사용
    • 모바일 환경에서는 또 다른데, <스타게이트>는 데스크탑만 고려해서 만들었기에 일단은 넘어가자.

출처: https://velog.io/@bcl0206/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%9E%80

  • 런타임 : JavaScript 코드가 실행되는 환경
    • 브라우저, Node.js, React-Native, Electron 등

이 부분은 JavaScript에서 핵심적인 부분이기에, 추후에 꼭 글을 작성하겠다.


3. 문제 해결

처음에 내가 생각해낸 문제 해결 방법은 다음과 같다.

  • V8엔진의 소스코드를 분석해서 시간을 보정하는 부분을 찾은 다음 코드에 넣으면 해결되지 않을까?

마침 <모익>에서 비슷한 문제를 발견했기에, 열심히 V8엔진과 Chrome의 소스코드를 뒤적거려봤지만 결과는...

 

런타임을 고려해 타이머 구현하기 / 모익

1. Why? 나는 핀테크 프로젝트 에서 회원관리 및 보안 처리를 담당했는데, 그 중에서 아래의 타이머 부분에 setInterval을 사용했다. 에서 setInterval을 사용한 글을 보고 왔다면 알겠지만, Js의 setInterval

cksxkr5193.tistory.com

위의 방법을 포기하고 나서, 대신 생각해낸 해결책은 이렇다.

  1. 브라우저를 탐색한다.
  2. firefox라면
    1. 가능하면 크롬/엣지로 접속을 권장하는 문장을 출력한다.
    2. firefox에 알맞게 setinterval delay값을 수정해준다.

window.navigator.userAgent

console.log(window.navigator.userAgent);

결과물

위와 같이 window.navigator.userAgent를 사용하면 브라우저를 탐지할 수 있다. Firefox/122.0이라는 결과값을 통해 Firefox 브라우저임을 알 수 있으며, 컴퓨터인지 모바일인지와 운영체제 등도 알 수 있다.

 

이제 window.navigator.userAgent에서 Firefox라는 값이 있으면, Chrome 혹은 Edge로 들어와달라고 문장을 출력해주자.

  useEffect(() => {
    const userAgent = window.navigator.userAgent;
    console.log(userAgent);
    if (userAgent.includes('Firefox')) {
      Swal.fire({
        icon: 'warning',
        title: '브라우저 권장 안내',
        text: '최적의 사용을 위해 Chrome 혹은 Edge 브라우저를 권장합니다.',
      });
    }
  }, []);

includes를 통해 값 안에 Firefox가 있다면 alert를 출력하도록 했다. 

짜잔!

그런데!

Mac OS의 브라우저들은 모두 WebKit 엔진을 사용하기 setInterval에 보정이 없을 확률이 높다. 따라서 경고문을 출력해주려고 했고, 우선 Safari를 위해 코드를 다음과 같이 수정했다.

if (userAgent.includes('Firefox') || userAgent.includes('Safari'))

그리고 나서 확인을 위해 Chrome으로 접속해봤더니, 다음과 같은 console.log가 출력되었다.

띠용

왜 safari가 은근슬쩍 끼여있는 것일까...? 어이가 없어서 찾아보니 이런 글을 찾을 수 있었다. 간단하게 요약하면

  • 모질라가 만든 넷스케이프 브라우저만 더 좋은 컨텐츠를 받음
  • 윈도우가 넷스케이프인척 하고 컨텐츠를 받으려고 익스플로러의 user agent 맨 앞에 'Mozilla'를 붙임
  • 넷스케이프가 밀려나버리고, 모질라는 다시 뛰어난 게코 엔진을 탑재한 파이어폭스를 만듦
  • 다른 브라우저들이 게코 엔진을 가져다쓰며 파이어폭스인척 하기위해 user agent에...(생략)

아무튼 이런 일이 반복된 결과, "구글 크롬"은 user agent값에 "나는 모질라이며 애플 웹킷이며 KHTML이며 게코와 유사하며 크롬이며 사파리임 ㅎㅎ"라는 걸 보내준다는 것이다.

 

그래서 어떻게 해야할까 잠깐 고민했는데, 찾아보니까 Mac OS랑 Windows는 정상적으로 출력되는걸 발견했다. 어차피 Mac OS에서 사용되는 브라우저는 죄다 Webkit 기반으로 구현되었기에 우선 Mac OS인지를 확인하고 경고문을 출력하도록 했다.

  useEffect(() => {
    const userAgent = window.navigator.userAgent;
    if (userAgent.includes('Mac')) {
      Swal.fire({
        icon: 'warning',
        title: '운영체제 권장 안내',
        text: '최적의 실행환경을 위해 Windows 운영체제를 권장합니다.',
      });
    } else if (userAgent.includes('Firefox')) {
      Swal.fire({
        icon: 'warning',
        title: '브라우저 권장 안내',
        text: '최적의 사용을 위해 Chrome 혹은 Edge 브라우저를 권장합니다.',
      });
    }
  }, []);

setTimeout에 delay값 적절하게 전달하기

계속해서 오차가 누적된다면, 누적된 만큼 delay 값을 적절하게 변경할 수 있도록 하면 괜찮지 않을까?

  const interval = 1000;

  let prevTime = Date.now(); 
  let sum = 0; // delay 누적치 저장용
  const step = () => {
    const currTime = Date.now();
    const diff = currTime - prevTime; 
    const delay = interval - (diff - interval);
    sum += delay - 1000;
    console.log(`delay : ${delay} sum : ${sum}`);
    prevTime = currTime;

    setTimeout(step, delay);
  };

  setTimeout(step, interval);

마침 적절한 코드가 있어 따라 구현했는데, 결과값이 예상과 너무 달랐다. 일단 애초에 setTimeout은 setInterval과 다르게 입력받은 시간 이상이 지나야 동작하기에 양수로 조금씩 누적이 일어날 줄 알았으나, 오히려 음수로 누적되고 있었다.

이 정도 수치면 물론 그냥 setInterval을 가져다 넣었을때보단 낫지만, 20초 정도만에 0.1초가량 오차가 생기는 건 여전히 좋지 않았다.

3분의 여유

그래서 임시방편으로 setInterval 수치를 조정하는 방식을 택했다. 기존과 같은 방식으로 setInterval 수치를 985로 입력한 후, 3분가량 기다려봤을 때, 일단 누적치가 최대 200ms를 벗어나지 않았기에 firefox 브라우저라면 setInterval의 수치를 985로 동작하도록 했다.


4. Future Work

  • setTimeout을 사용했는데 음수로 발산하는 이유 찾아보기
  • Mac OS에서의 동작 확인해보기
  • Firefox에서의 setInterval 더 좋은 방안으로 수정하기

5. 레퍼런스

https://www.jeong-min.com/49-js-runtime/

 

개발자 단민 | 자바스크립트 엔진: "어, 아직 싱글이야" | JS 런타임에 대해 알아보자

자바스크립트 엔진과 런타임에 대해 이야기하기 전에, 자바스크립트 자체가 무엇인지 이해하는 것도 중요하다! 자바스크립트는 웹 개발에서 가장 널리 사용되는 프로그래밍 언어 중 하나로, 웹

www.jeong-min.com

https://gurtn.tistory.com/214

 

[JS] 접속한 브라우저 체크하기

예시코드 See the Pen Untitled by hyukson (@hyukson) on CodePen. 전체 코드 function getBrowser() { const browsers = [ 'Chrome', 'Opera', 'WebTV', 'Whale', 'Beonex', 'Chimera', 'NetPositive', 'Phoenix', 'Firefox', 'Safari', 'SkipStone', 'Netscape', '

gurtn.tistory.com

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent

 

Navigator: userAgent property - Web APIs | MDN

The Navigator.userAgent read-only property returns the user agent string for the current browser.

developer.mozilla.org

https://velog.io/@ktthee/%EC%9E%AC%EB%AF%B8%EC%9E%88%EB%8A%94-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-user-agent-%EC%9D%B4%EC%95%BC%EA%B8%B0

 

velog

 

velog.io