2 điểm bởi GN⁺ 2025-03-31 | 1 bình luận | Chia sẻ qua WhatsApp
  • Nguyên nhân khiến trang của David Bushell trông bị vỡ trong thời gian dài với một số người dùng là do tiện ích mở rộng trình duyệt Grammarly bí mật chèn CSS vào trang
  • Trên Firefox, tiện ích Grammarly chèn stylesheet từ tài nguyên mở rộng cục bộ, khó bị trang web phát hiện qua StyleSheetList và còn vượt qua cả Content Security Policy
  • Xung đột xảy ra vì Grammarly định nghĩa toàn cục --rem:16 trên :root, trong khi trang cũng dùng --rem cho phép tính typography linh hoạt với cùng tên
  • --rem phía trang nằm bên trong cascade layer, và do quy tắc CSS rằng style ngoài layer được ưu tiên hơn, giá trị của Grammarly có thể ghi đè lên phép tính
  • Tạm thời tác giả cầm cự bằng mutation observer và !important, nhưng cách xử lý cuối cùng là đổi tên thuộc tính thành --🤡; nếu tiện ích chèn những tên thông thường vào :root toàn cục thì rất dễ xung đột với trang web

CSS của Grammarly đã đi vào bên trong trang

  • Trong nhiều tháng đã có những báo cáo lẻ tẻ rằng bố cục trang bị lệch và kích thước hiển thị kỳ lạ, kèm theo cả ảnh chụp màn hình
  • Những độc giả am hiểu kỹ thuật đã chỉ ra Grammarly browser extension là nguyên nhân chính, và David Bushell đã tự cài nó trên Mullvad browser dựa trên Firefox để kiểm chứng
  • Khi cài tiện ích, các quyền bao gồm
    • truy cập dữ liệu của mọi website
    • hiển thị thông báo
    • truy cập tab trình duyệt
  • Grammarly chèn vào trang web một stylesheet được tải từ tài nguyên mở rộng cục bộ
    • stylesheet này không thể bị trang web tìm thấy qua StyleSheetList
    • nó cũng vượt qua Content Security Policy
    • trên Firefox, nó hoạt động như một stylesheet tàng hình mà bản thân website rất khó phát hiện
  • Tiện ích còn thêm phần tử tùy biến <grammarly-desktop-integration> vào tài liệu <html> của mọi website ngay cả khi người dùng không hề tương tác

Chỉ một cái tên --rem đã làm hỏng bố cục như thế nào

  • Ở cuối stylesheet của Grammarly có đoạn CSS sau
:host,
:root {
  --rem:16
}
  • Ở các phần khác của cùng stylesheet, --rem được dùng để tính cỡ chữ và chiều cao dòng
.kE2Bj {
  font-size:calc(0.86px*(var(--rem) - 2));
  line-height:calc(1.2868px*(var(--rem) - 2));
}
  • Bản thân trang cũng đang dùng thuộc tính tùy biến --rem cho thử nghiệm typography linh hoạt của mình
@layer base {
  :root {
    --rem: 0.0625rem;
    --fluid: calc((100vi - (400 * var(--rem))) / (1920 - 400));
    --font-size-h1: clamp(
      calc(31 * var(--rem)),
      calc((31 * var(--rem)) + (80 - 31) * var(--fluid)),
      calc(80 * var(--rem))
    );
  }
}
  • --rem của trang được định nghĩa bên trong cascade layer, còn style ngoài layer thì được ưu tiên hơn style trong layer bất kể CSS specificity
    • thứ tự nguồn cũng có ảnh hưởng, nên --rem của Grammarly có thể đã thắng
    • kết quả là các công thức tính của trang bị phá vỡ và gây ra lỗi bố cục
  • Ban đầu, tác giả đối phó bằng cách dùng mutation observer để phát hiện web component được thêm vào rồi áp thêm style !important
  • Sau khi xác định đúng nguyên nhân, tác giả đổi tên thuộc tính tùy biến của trang thành --🤡
    • đây là một tên thuộc tính tùy biến hợp lệ trong CSS
    • --rem trở thành một cái tên có nguy cơ xung đột vì Grammarly dùng nó ở phạm vi toàn cục
  • Grammarly tạo ra tên class ngẫu nhiên nhưng lại áp một tên thuộc tính tùy biến rất phổ biến là --rem toàn cục trên :root, đồng thời chèn mã vào mọi trang web ngay cả khi người dùng không thực sự dùng tiện ích
  • Tác giả đã liên hệ với bộ phận hỗ trợ của Grammarly, nhưng hiện vẫn chưa gặp được người phụ trách kỹ thuật hiểu rõ vấn đề

1 bình luận

 
GN⁺ 2025-03-31
Ý kiến trên Hacker News
  • Trường hợp gặp phải do vấn đề extension của tôi hơi khác một chút. Chúng tôi đang phát hành một extension giúp dễ dàng chuyển đổi máy chủ proxy để kiểm thử vị trí địa lý.
    Vài tháng trước, chúng tôi có buổi demo khách hàng tệ hại nhất: sản phẩm trông như thể hoàn toàn không hoạt động. Sau một hồi debug, chúng tôi phát hiện bản cập nhật gần đây của extension 1Password đã làm hỏng extension của chúng tôi. 1Password đã subscribe sự kiện xác thực nhưng không trả về, dẫn đến timeout, nên subscriber của chúng tôi không được gọi. Extension của chúng tôi đã yêu cầu trình duyệt đổi máy chủ proxy rồi sẵn sàng cung cấp thông tin xác thực, nhưng request lại không bao giờ đến. Đội hỗ trợ 1Password khá hơn Grammarly, nhưng rất khó thuyết phục một PM không rõ là ai ưu tiên vấn đề thông qua đội hỗ trợ.
    Sau đó chúng tôi biết được rằng một extension nào đó cần cho các trang web chính phủ Nga cũng gặp cùng vấn đề.

    • Tình huống tương tự. 1Password vẫn làm hỏng khả năng mở UI side panel của Chrome từ content script của extension khác. Nó làm hỏng cờ tin cậy cho biết sự kiện bắt nguồn từ tương tác người dùng.
      Với tư cách người đã làm về extension hơn 10 năm, rốt cuộc Google chịu trách nhiệm lớn. Bỏ qua vấn đề chính trị quanh thay đổi đối với ad blocker, Manifest v3 ở nhiều khía cạnh tệ hơn kỳ vọng rất nhiều.
      Nhìn chung, tôi có cảm giác chất lượng codebase Chromium đã giảm khá nhiều so với trước.
  • Nếu bạn tiêm script hoặc style vào một trang không biết trước, tối thiểu cũng phải tách namespace cho biến.

    • Điều này thật sự khiến tôi bực mình: khoảng 5–6 tháng trước, trong một buổi phỏng vấn, tôi kể về startup Instagram/branding nơi tôi từng làm CTO kiêm lập trình viên chính vào năm 2014. Tôi giải thích rằng hồi đó tôi đã xây dựng hệ thống build để đảm bảo các CSS class và đối tượng JavaScript được tách namespace đúng cách, không có khả năng xung đột, đồng thời quản lý chính xác script nào cần load tùy theo widget nào có trên site khách hàng bên thứ ba.
      Nhưng người phỏng vấn lại gạt đi kiểu như những thứ đó giờ công cụ đều làm hết rồi và ai cũng làm vậy. Tôi phần nào buộc phải đồng ý, vì giờ tôi không còn làm việc đó nên thực tế cũng không biết. Nhưng hóa ra không phải ai cũng làm như vậy.
    • Tách namespace không chỉ tốt cho người khác mà còn tiện cho chính mình. Ở công ty trước, chúng tôi làm automation trình duyệt không hiển thị cho người dùng; nó thậm chí không phải extension, nhưng tách namespace vẫn hữu ích.
      Chúng tôi có thể phân biệt rõ cái gì do mình chèn vào với cái vốn đã có, đồng thời tránh được các xung đột tiềm ẩn.
    • Tôi rời mảng frontend đã lâu, hiện nay tách namespace CSS thường được xử lý bằng cách nào?
    • Tốt hơn nữa là dùng Shadow DOM.
  • Thật đáng sợ khi thấy kẻ xâm nhập màu xanh đó được cài mặc định trên mọi website trong các buổi chia sẻ màn hình hay ghi hình. Không chỉ là vấn đề gây khó chịu về mặt thị giác, mà còn kéo theo quyền riêng tư và một vector tấn công rõ ràng.
    Chrome có thể chỉ bật extension khi cần, nhưng tôi không hiểu vì sao không ai làm vậy. Tôi cũng tự hỏi vì sao đó không phải là mặc định của mọi trình duyệt.

    • Tôi thấy mình khá may mắn vì có đồng nghiệp quan tâm đến những chuyện như vậy. Đã có lúc chúng tôi dừng cuộc họp khi thấy rõ một số người tham gia đã cài một extension cụ thể hoặc nhiều loại trợ lý AI.
      Một số đồng nghiệp không thoải mái với khả năng thông tin bị chuyển cho bên thứ ba, nên họ tạm dừng cuộc họp cho đến khi extension được tắt.
  • Tôi là kỹ sư của Grammarly Extension. Trước hết, chúng tôi thành thật xin lỗi vì extension của chúng tôi đã làm hỏng trải nghiệm người dùng trên dbushell.com và khiến tác giả phải tốn thời gian, công sức để tìm nguyên nhân.
    Đây không phải điều chúng tôi mong muốn, và chúng tôi đang dùng nhiều kỹ thuật để tránh chuyện như vậy xảy ra. Nhưng rõ ràng như vậy là chưa đủ, và bài viết cho thấy có nhiều điểm cần cải thiện.
    Như một bản sửa nhanh, chúng tôi đã thêm ngoại lệ tạm thời cho dbushell.com. Đồng thời, chúng tôi đang làm một thay đổi để bảo đảm cô lập style phù hợp; những vấn đề như thế này tuyệt đối không nên xảy ra.

  • Tôi gặp vấn đề tương tự khi Google Translate làm hỏng web app của mình. Người dùng dùng Google Translate rồi phàn nàn rằng app của tôi bị lỗi, nhưng thực tế là Google đã thay đổi trạng thái app ở một tầng meta cao hơn. Đây thật sự là một thực hành rất tệ.
    Tôi đang cố phát hiện Google Translate để hiển thị cảnh báo.

    • Có thể liên quan đến trường hợp hai ngày trước: https://www.pewresearch.org/decoded/2025/03/21/how-a-glitch-... / https://news.ycombinator.com/item?id=43441880
    • Sự can thiệp của Google Translate gây khó chịu, nhưng với các công cụ trình duyệt hiện tại, tôi nghĩ thực ra khó để nó hoạt động theo cách khác.
      Ví dụ, đôi khi cần dịch một câu như “nhấp [vào đây] để xem thêm thông tin”. Khi chuyển sang ngôn ngữ khác, có thể phải đưa liên kết về cuối câu, thành kiểu “để xem thêm thông tin, hãy [nhấp vào đây]”. Để làm vậy cần sắp xếp lại các phần tử DOM, và việc này có thể xung đột với ứng dụng tương tác.
      Đội Google Translate có thể làm nhiều việc để giảm can thiệp, nhưng tôi nghĩ sẽ khó loại bỏ hoàn toàn nếu không có API trình duyệt mới.
  • Đã chuyển cho đội kỹ thuật.

    • Khá khó chịu khi những bản sửa một dòng như thế này bị bỏ mặc lâu trong địa ngục backlog. Tôi muốn làm ở công ty nơi lập trình viên nói “sửa luôn bây giờ còn nhanh hơn viết ticket, nên cứ làm thôi”.
      Ở chỗ tôi làm, việc mọi người không làm vậy khiến tôi phát điên. Ngay cả giám đốc kỹ thuật cũng thêm vào ticket của mình những việc mất ít thời gian hơn so với cứ xử lý luôn. Dù vậy, việc thường xuyên nghe câu “tôi không tạo ticket để gửi tin nhắn, mà đã nhắn thẳng cho người đó theo cách của bạn” là một tín hiệu tốt.
  • Ở công ty có rất nhiều lỗi Sentry phát sinh do tiện ích mở rộng trình duyệt làm những chuyện kỳ lạ
    Google Translate của Chrome cũng nổi tiếng là làm hỏng các trang dựa trên React
    Cuối cùng, việc này biến thành công việc phân loại nhàm chán: lần lượt bỏ qua từng vấn đề do tiện ích mới gây ra. Chúng tôi đang dùng lọc phía client để giảm lượng thu thập. Nhìn chung nhiễu nhiều hơn backend, nên phải đặt ngưỡng cao hơn rất nhiều

    • Đây không chỉ là nhiễu đơn thuần. Thực tế người dùng đang gặp crash hoặc các vấn đề khác vì chuyện đó. Có một bài viết chi tiết về việc tiện ích Google Translate can thiệp vào React và các web app khác: https://martijnhols.nl/blog/everything-about-google-translat...
      Frontend có nhiều lỗi hơn hẳn cũng không có gì đáng ngạc nhiên. Vì nó phải hỗ trợ nhiều biến thể client hơn rất nhiều so với một backend thông thường. Làm một web app lớn hoạt động tốt cho tất cả mọi người có thể cực kỳ khó
    • Bạn đang nói đến lỗi “Object captured as exception” à? Nếu là lỗi mà Sentry chẳng đưa ra hướng dẫn gì đó, thì chúng tôi cứ lọc nó ở phía client luôn
  • Tò mò nếu tiêm một biến có thể làm hỏng web nhiều nhất thì sẽ là gì. Tôi nghĩ đến cái này:
    --primary-color: transparent

    • --serif: "Comic Sans MS"
  • Nên đối phó thế nào với các tiện ích mở rộng trình duyệt mang tính đối địch?

    • Đây là kiểu phàn nàn tôi thích nhất trên website cộng đồng tôi vận hành. “Ảnh không hiện trên trang quảng cáo.” Bạn có dùng trình chặn quảng cáo không? “Có.” Bạn nghĩ trình chặn quảng cáo làm gì...
    • Có lẽ có thể định nghĩa trạng thái hợp lệ của DOM trang, rồi vài giây sau khi tải trang xong thì quét và xóa các phần tử cũng như CSS style “đối địch”
      Vừa nghĩ vậy tôi mở đại một trang của The Guardian bằng DevTools, thì thấy ai đó đã chèn script và iframe trỏ đến twitter.com
    • Trong trường hợp này, tôi thấy gọi là ‘đối địch’ thì hơi quá. ‘Thiếu năng lực’ là đủ rồi. Chỉ là nhiều âm tiết hơn
      Tôi không thích Grammarly hay mô hình công nghệ của họ, nhưng quy ác ý cho chuyện có thể giải thích đầy đủ bằng sự ngu ngốc thì không công bằng
      Tôi đã không làm frontend lâu rồi, nhưng chẳng phải cả tiện ích Grammarly lẫn code của chính mình đều nên dùng tên thuộc tính có namespace tách biệt sao?
    • Đến lúc này thì tôi hoàn toàn không cài tiện ích mở rộng trình duyệt nữa
    • Gỡ nó đi là được mà?
  • Tôi nghĩ có thể lợi dụng chuyện này để hijack plugin đó. Ít nhất có vẻ có thể tiêm văn bản, và có lẽ còn render được một form đăng nhập đẹp mắt để lợi dụng niềm tin của người dùng vào tiện ích
    Việc tiêm phần tử vào tài liệu do người khác kiểm soát có thật sự an toàn không?

    • Tôi không hiểu cơ chế đó sẽ hoạt động thế nào. Họ tiêm CSS vào trang của bạn, nhưng website không thể tiêm thứ gì đó vào UI của tiện ích mở rộng
      Việc có thể làm chỉ là bắt chước UI của tiện ích trong chính website, mà cái đó thì đâu cần tiêm gì. Chỉ cần sao chép thiết kế là được