Mô phỏng tình trạng thiếu thị lực màu trong Trình kết xuất Blink

Mathias Bynens
Mathias Bynens

Bài viết này mô tả lý do và cách chúng tôi triển khai hoạt động mô phỏng sự thiếu hụt thị lực màu trong Công cụ cho nhà phát triển và Trình kết xuất đường liên kết.

Nền: độ tương phản màu kém

Văn bản có độ tương phản thấp là vấn đề phổ biến nhất về khả năng hỗ trợ tiếp cận tự động phát hiện được trên web.

Danh sách các vấn đề thường gặp về khả năng hỗ trợ tiếp cận trên web. Cho đến nay, văn bản có độ tương phản thấp là vấn đề thường gặp nhất.

Theo kết quả phân tích khả năng tiếp cận của WebAIM đối với 1 triệu trang web hàng đầu, hơn 86% số trang chủ có độ tương phản thấp. Trung bình, mỗi trang chủ có 36 phiên bản riêng biệt văn bản có độ tương phản thấp.

Sử dụng Công cụ cho nhà phát triển để tìm, hiểu và khắc phục các vấn đề về độ tương phản

Công cụ của Chrome cho nhà phát triển có thể giúp các nhà phát triển và nhà thiết kế cải thiện độ tương phản và chọn bảng phối màu dễ truy cập hơn cho các ứng dụng web:

Gần đây, chúng tôi đã thêm một công cụ mới vào danh sách này và công cụ này hơi khác so với các công cụ khác. Các công cụ trên chủ yếu tập trung vào thông tin về tỷ lệ tương phản hiển thị và cung cấp cho bạn các lựa chọn để khắc phục. Chúng tôi nhận ra rằng Công cụ cho nhà phát triển vẫn còn thiếu một cách để nhà phát triển understanding về không gian vấn đề này. Để giải quyết vấn đề này, chúng tôi đã triển khai hoạt động mô phỏng tình trạng thiếu thị lực trong thẻ Hiển thị cho công cụ cho nhà phát triển.

Trong Puppeteer, API page.emulateVisionDeficiency(type) mới cho phép bạn bật những hoạt động mô phỏng này theo phương thức lập trình.

Khiếm khuyết thị giác màu

Khoảng 1/20 người bị mắc hội chứng mù màu (còn được gọi là thuật ngữ "mù màu" kém chính xác hơn). Những khiếm khuyết như vậy khiến khó phân biệt các màu khác nhau, điều này có thể làm tăng vấn đề về độ tương phản.

Bức tranh đầy màu sắc về bút sáp màu tan chảy, được mô phỏng không bị khiếm khuyết về thị giác màu
Bức ảnh nhiều màu sắc về bút sáp màu tan chảy, không mô phỏng khuyết tật thị giác màu.
ALT_TEXT_HERE
Tác động của việc mô phỏng chứng mù màu đối với bức tranh đầy màu sắc về bút sáp màu tan chảy.
Tác động của việc mô phỏng chứng mù màu xanh lục trên bức ảnh đầy màu sắc về bút sáp màu tan chảy.
Tác động của việc mô phỏng chứng mù màu đối với bức tranh đầy màu sắc về bút sáp màu tan chảy.
Tác động của việc mô phỏng chứng mù màu đối với bức ảnh đầy màu sắc về bút sáp màu tan chảy.
Tác động của việc mô phỏng chứng mù màu trên bức tranh đầy màu sắc về bút sáp màu tan chảy.
Tác động của việc mô phỏng chứng mù màu đối với bức ảnh đầy màu sắc về bút sáp màu tan chảy.
Tác động của việc mô phỏng chứng mù màu đối với bức tranh đầy màu sắc về bút sáp màu tan chảy.

Là một nhà phát triển có thị giác bình thường, bạn có thể thấy Công cụ cho nhà phát triển hiển thị tỷ lệ tương phản kém cho các cặp màu mà bạn thấy. Điều này xảy ra vì công thức tỷ lệ tương phản có tính đến các lỗi thị giác màu này! Bạn có thể vẫn đọc được văn bản có độ tương phản thấp trong một số trường hợp, nhưng những người khiếm thị không có đặc quyền đó.

Bằng cách cho phép nhà thiết kế và nhà phát triển mô phỏng ảnh hưởng của những khiếm khuyết về thị giác này trên các ứng dụng web của riêng họ, chúng tôi mong muốn cung cấp mảnh ghép còn thiếu: Công cụ cho nhà phát triển không chỉ giúp bạn tìmkhắc phục các vấn đề về độ tương phản, giờ đây bạn còn có thể hiểu chúng!

Mô phỏng lỗi thị giác màu bằng HTML, CSS, SVG và C++

Trước khi tìm hiểu sâu hơn về việc triển khai Trình kết xuất Blink của tính năng này, chúng ta sẽ hiểu cách bạn sẽ triển khai chức năng tương đương bằng công nghệ web.

Bạn có thể xem mỗi ví dụ mô phỏng về tình trạng thiếu thị lực màu như một lớp phủ trên toàn bộ trang. Nền tảng web có một cách để làm điều đó: bộ lọc CSS! Với thuộc tính filter của CSS, bạn có thể sử dụng một số hàm bộ lọc định sẵn, chẳng hạn như blur, contrast, grayscale, hue-rotate và nhiều hàm khác. Để có nhiều quyền kiểm soát hơn nữa, thuộc tính filter cũng chấp nhận một URL có thể trỏ đến định nghĩa bộ lọc SVG tuỳ chỉnh:

<style>
  :root {
    filter: url(#deuteranopia);
  }
</style>
<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

Ví dụ trên sử dụng định nghĩa bộ lọc tuỳ chỉnh dựa trên ma trận màu. Về mặt lý thuyết, giá trị màu [Red, Green, Blue, Alpha] của mỗi pixel được nhân ma trận để tạo ra màu mới [R′, G′, B′, A′].

Mỗi hàng trong ma trận chứa 5 giá trị: một số nhân cho (từ trái sang phải) R, G, B và A, cũng như giá trị thứ năm cho giá trị dịch chuyển không đổi. Có 4 hàng: hàng đầu tiên của ma trận được dùng để tính giá trị Màu đỏ mới, hàng thứ hai Xanh lục, hàng thứ ba Xanh dương và hàng cuối cùng Alpha.

Bạn có thể thắc mắc những con số chính xác trong ví dụ của chúng tôi đến từ đâu. Điều gì khiến ma trận màu này có giá trị xấp xỉ tốt với mù màu? Câu trả lời là: khoa học! Các giá trị này dựa trên mô hình mô phỏng tình trạng thiếu hụt thị lực về mặt sinh lý do Machado, Oliveira và Fernandes thực hiện.

Dù sao thì chúng ta cũng có bộ lọc SVG này và hiện có thể áp dụng bộ lọc này cho các phần tử tùy ý trên trang bằng cách sử dụng CSS. Chúng ta có thể lặp lại tương tự cho các chứng khiếm thị khác. Sau đây là bản minh hoạ cách triển khai:

Nếu muốn, chúng tôi có thể xây dựng tính năng Công cụ cho nhà phát triển như sau: khi người dùng mô phỏng một khiếm khuyết về thị giác trong giao diện người dùng Công cụ cho nhà phát triển, chúng tôi sẽ chèn bộ lọc SVG vào tài liệu được kiểm tra, sau đó chúng tôi áp dụng kiểu bộ lọc trên phần tử gốc. Tuy nhiên, có một vài vấn đề với phương pháp đó:

  • Trang có thể đã có bộ lọc trên thành phần gốc mà mã của chúng tôi có thể ghi đè sau đó.
  • Trang có thể đã có một phần tử có thuộc tính id="deuteranopia" xung đột với định nghĩa bộ lọc của chúng tôi.
  • Trang có thể dựa vào một cấu trúc DOM nhất định và nếu chèn <svg> vào DOM, chúng ta có thể vi phạm những giả định này.

Về các trường hợp phức tạp, vấn đề chính của phương pháp này là chúng tôi sẽ thực hiện các thay đổi có thể quan sát bằng phương pháp lập trình đối với trang. Nếu người dùng Công cụ cho nhà phát triển kiểm tra DOM, họ có thể đột nhiên thấy một phần tử <svg> mà họ chưa bao giờ thêm hoặc một phần tử CSS filter mà họ chưa bao giờ ghi. Điều đó sẽ khó hiểu! Để triển khai chức năng này trong Công cụ cho nhà phát triển, chúng ta cần một giải pháp không có những hạn chế này.

Hãy cùng xem cách chúng ta có thể hạn chế việc này. Giải pháp này có 2 phần mà chúng ta cần ẩn: 1) kiểu CSS với thuộc tính filter và 2) định nghĩa bộ lọc SVG (hiện đang nằm trong DOM).

<!-- Part 1: the CSS style with the filter property -->
<style>
  :root {
    filter: url(#deuteranopia);
  }
</style>
<!-- Part 2: the SVG filter definition -->
<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

Tránh phần phụ thuộc SVG trong tài liệu

Hãy bắt đầu bằng phần 2: làm cách nào để chúng ta tránh thêm SVG vào DOM? Bạn nên di chuyển hình ảnh đó sang một tệp SVG riêng biệt. Chúng ta có thể sao chép <svg>…</svg> từ HTML ở trên và lưu dưới dạng filter.svg, nhưng trước tiên, chúng ta cần thực hiện một số thay đổi! SVG nội tuyến trong HTML tuân theo các quy tắc phân tích cú pháp HTML. Điều đó có nghĩa là bạn có thể thực hiện được những việc như bỏ qua dấu ngoặc kép xung quanh giá trị thuộc tính trong một số trường hợp. Tuy nhiên, SVG trong các tệp riêng biệt phải là XML hợp lệ và việc phân tích cú pháp XML nghiêm ngặt hơn so với HTML. Dưới đây là đoạn mã SVG-in-HTML của chúng tôi:

<svg>
  <filter id="deuteranopia">
    <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000">
    </feColorMatrix>
  </filter>
</svg>

Để biến tệp SVG độc lập hợp lệ này (và tệp XML), chúng ta cần thực hiện một số thay đổi. Bạn có đoán được bản đồ nào không?

<svg xmlns="http://www.w3.org/2000/svg">
 
<filter id="deuteranopia">
   
<feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                           0.280  0.673  0.047  0.000  0.000
                          -0.012  0.043  0.969  0.000  0.000
                           0.000  0.000  0.000  1.000  0.000"
/>
 
</filter>
</svg>

Thay đổi đầu tiên là phần khai báo không gian tên XML ở trên cùng. Phép bổ sung thứ hai là yếu tố có tên là “solid” – dấu gạch chéo cho biết thẻ <feColorMatrix> vừa mở vừa đóng phần tử. Thay đổi cuối cùng này thực sự không cần thiết (chúng ta có thể chỉ cần sử dụng thẻ đóng </feColorMatrix> rõ ràng), nhưng vì cả XML và SVG-in-HTML đều hỗ trợ viết tắt /> này, nên chúng ta cũng có thể sử dụng nó.

Dù sao, với những thay đổi đó, cuối cùng chúng ta cũng có thể lưu tệp này dưới dạng tệp SVG hợp lệ và trỏ đến tệp này từ giá trị thuộc tính filter của CSS trong tài liệu HTML của chúng ta:

<style>
  :root {
    filter: url(filters.svg#deuteranopia);
  }
</style>

Hurrah thân mến! Chúng ta không còn phải chèn SVG vào tài liệu này nữa! Như vậy đã tốt hơn nhiều. Nhưng... giờ đây chúng tôi phụ thuộc vào một tệp riêng. Đó vẫn là một phần phụ thuộc. Chúng ta có thể loại bỏ nó bằng cách nào đó không?

Kết quả là chúng ta thực sự không cần tệp. Chúng ta có thể mã hoá toàn bộ tệp trong URL bằng URL dữ liệu. Để làm điều này xảy ra, chúng ta lấy nội dung của tệp SVG mà chúng ta có trước đó, thêm tiền tố data:, định cấu hình loại MIME thích hợp và chúng ta đã có một URL dữ liệu hợp lệ đại diện cho cùng một tệp SVG:

data:image/svg+xml,
  <svg xmlns="http://www.w3.org/2000/svg">
    <filter id="deuteranopia">
      <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000
                             0.280  0.673  0.047  0.000  0.000
                            -0.012  0.043  0.969  0.000  0.000
                             0.000  0.000  0.000  1.000  0.000" />
    </filter>
  </svg>

Lợi ích là giờ đây, chúng ta không cần phải lưu trữ tệp ở bất cứ đâu hoặc tải tệp từ ổ đĩa hoặc qua mạng để chỉ sử dụng trong tài liệu HTML. Vì vậy, thay vì tham chiếu đến tên tệp như trước đây, giờ đây, chúng ta có thể trỏ đến URL dữ liệu:

<style>
  :root {
    filter: url('data:image/svg+xml,\
      <svg xmlns="http://www.w3.org/2000/svg">\
        <filter id="deuteranopia">\
          <feColorMatrix values="0.367  0.861 -0.228  0.000  0.000\
                                 0.280  0.673  0.047  0.000  0.000\
                                -0.012  0.043  0.969  0.000  0.000\
                                 0.000  0.000  0.000  1.000  0.000" />\
        </filter>\
      </svg>#deuteranopia');
  }
</style>

Ở cuối URL, chúng ta vẫn chỉ định mã của bộ lọc mà chúng ta muốn sử dụng, giống như trước đây. Lưu ý rằng bạn không cần phải mã hoá Base64 tài liệu SVG trong URL vì làm như vậy sẽ chỉ ảnh hưởng đến khả năng đọc và tăng kích thước tệp. Chúng tôi thêm dấu gạch chéo ngược ở cuối mỗi dòng để đảm bảo các ký tự dòng mới trong URL dữ liệu không chấm dứt chuỗi ký tự CSS.

Cho đến nay, chúng tôi chỉ nói về cách mô phỏng khiếm khuyết về thị giác bằng công nghệ web. Điều thú vị là cách triển khai cuối cùng của chúng tôi trong Trình kết xuất Blink thực sự khá giống nhau. Dưới đây là một tiện ích trợ giúp C++ mà chúng tôi đã thêm để tạo URL dữ liệu với định nghĩa bộ lọc nhất định, dựa trên cùng một kỹ thuật:

AtomicString CreateFilterDataUrl(const char* piece) {
  AtomicString url =
      "data:image/svg+xml,"
        "<svg xmlns=\"http://www.w3.org/2000/svg\">"
          "<filter id=\"f\">" +
            StringView(piece) +
          "</filter>"
        "</svg>"
      "#f";
  return url;
}

Và dưới đây là cách chúng ta sử dụng nó để tạo tất cả các bộ lọc cần thiết:

AtomicString CreateVisionDeficiencyFilterUrl(VisionDeficiency vision_deficiency) {
  switch (vision_deficiency) {
    case VisionDeficiency::kAchromatopsia:
      return CreateFilterDataUrl("…");
    case VisionDeficiency::kBlurredVision:
      return CreateFilterDataUrl("<feGaussianBlur stdDeviation=\"2\"/>");
    case VisionDeficiency::kDeuteranopia:
      return CreateFilterDataUrl(
          "<feColorMatrix values=\""
          " 0.367  0.861 -0.228  0.000  0.000 "
          " 0.280  0.673  0.047  0.000  0.000 "
          "-0.012  0.043  0.969  0.000  0.000 "
          " 0.000  0.000  0.000  1.000  0.000 "
          "\"/>");
    case VisionDeficiency::kProtanopia:
      return CreateFilterDataUrl("…");
    case VisionDeficiency::kTritanopia:
      return CreateFilterDataUrl("…");
    case VisionDeficiency::kNoVisionDeficiency:
      NOTREACHED();
      return "";
  }
}

Lưu ý rằng kỹ thuật này cho phép chúng ta khai thác toàn bộ sức mạnh của bộ lọc SVG mà không phải triển khai lại bất cứ thứ gì hoặc phát minh lại bất kỳ bánh xe nào. Chúng tôi đang triển khai tính năng Trình kết xuất Blink, nhưng chúng tôi đang triển khai bằng cách tận dụng Nền tảng web.

Tốt rồi, vậy là chúng ta đã tìm ra cách tạo bộ lọc SVG và chuyển chúng thành URL dữ liệu mà chúng ta có thể sử dụng trong giá trị thuộc tính filter của CSS. Bạn có nghĩ ra vấn đề với kỹ thuật này không? Hoá ra chúng tôi thực sự không thể dựa vào URL dữ liệu được tải trong mọi trường hợp, vì trang đích có thể có một Content-Security-Policy chặn URL dữ liệu. Khi triển khai cấp đường liên kết cuối cùng, chúng tôi cần đặc biệt lưu ý bỏ qua CSP cho các URL dữ liệu "nội bộ" này trong quá trình tải.

Về các vấn đề phức tạp, chúng tôi đã có một số bước tiến tốt. Do không còn phụ thuộc vào <svg> cùng dòng trong cùng một tài liệu, nên chúng tôi đã rút gọn giải pháp của mình xuống chỉ còn một định nghĩa thuộc tính CSS filter độc lập duy nhất. Vậy thì tuyệt quá! Bây giờ, cũng hãy bỏ qua phần này.

Tránh phần phụ thuộc CSS trong tài liệu

Xin tóm lại, đây là tiến độ của chúng tôi cho đến thời điểm này:

<style>
  :root {
    filter: url('data:…');
  }
</style>

Chúng tôi vẫn phụ thuộc vào thuộc tính filter của CSS này. Thuộc tính này có thể ghi đè filter trong tài liệu thực và làm hỏng mọi thứ. Nó cũng sẽ xuất hiện khi kiểm tra các kiểu đã tính toán trong Công cụ cho nhà phát triển nên sẽ gây nhầm lẫn. Chúng tôi có thể tránh những vấn đề này bằng cách nào? Chúng ta cần tìm cách để thêm bộ lọc vào tài liệu mà không khiến các nhà phát triển có thể quan sát bằng phương thức lập trình.

Một ý tưởng nảy ra là tạo một thuộc tính CSS nội bộ của Chrome mới hoạt động giống như filter, nhưng có tên khác như --internal-devtools-filter. Sau đó, chúng ta có thể thêm logic đặc biệt để đảm bảo thuộc tính này không bao giờ hiển thị trong Công cụ cho nhà phát triển hoặc trong các kiểu đã tính toán trong DOM. Thậm chí, chúng ta có thể đảm bảo rằng API này chỉ hoạt động trên một phần tử mà chúng ta cần: thành phần gốc. Tuy nhiên, giải pháp này không lý tưởng cho bạn: chúng tôi sẽ sao chép chức năng đã có trong filter và ngay cả khi cố gắng ẩn thuộc tính phi tiêu chuẩn này, nhà phát triển web vẫn có thể tìm ra và bắt đầu sử dụng thuộc tính này. Điều này sẽ ảnh hưởng xấu đến Nền tảng web. Chúng ta cần một số cách khác để áp dụng kiểu CSS mà không thể quan sát được trong DOM. Google có ý tưởng nào không?

Thông số kỹ thuật CSS có một phần giới thiệu mô hình định dạng hình ảnh được sử dụng và một trong các khái niệm chính là khung nhìn. Đây là chế độ xem trực quan mà người dùng tham khảo trên trang web. Một khái niệm có liên quan chặt chẽ là khối chứa ban đầu, giống như khung nhìn có thể định kiểu <div> chỉ tồn tại ở cấp thông số kỹ thuật. Thông số kỹ thuật đề cập đến khái niệm “khung nhìn” ở khắp nơi. Ví dụ: bạn biết cách trình duyệt hiển thị thanh cuộn khi nội dung không phù hợp? Tất cả đều được xác định trong thông số kỹ thuật của CSS, dựa trên "khung nhìn" này.

viewport này cũng tồn tại trong Trình kết xuất đường liên kết (Blink Renderer), dưới dạng thông tin chi tiết về hoạt động triển khai. Sau đây là mã áp dụng kiểu khung nhìn mặc định theo thông số kỹ thuật:

scoped_refptr<ComputedStyle> StyleResolver::StyleForViewport() {
  scoped_refptr<ComputedStyle> viewport_style =
      InitialStyleForElement(GetDocument());
  viewport_style->SetZIndex(0);
  viewport_style->SetIsStackingContextWithoutContainment(true);
  viewport_style->SetDisplay(EDisplay::kBlock);
  viewport_style->SetPosition(EPosition::kAbsolute);
  viewport_style->SetOverflowX(EOverflow::kAuto);
  viewport_style->SetOverflowY(EOverflow::kAuto);
  // …
  return viewport_style;
}

Bạn không cần phải hiểu C++ hoặc những chi tiết phức tạp của công cụ Kiểu của Blink để thấy rằng mã này xử lý khung nhìn (hoặc chính xác hơn: khối ban đầu chứa các khối) z-index, display, positionoverflow. Đó là tất cả những khái niệm mà có thể bạn đã quen thuộc trong CSS! Có một số điều kỳ diệu khác liên quan đến ngữ cảnh xếp chồng, vốn không trực tiếp dịch sang thuộc tính CSS, nhưng về tổng thể, bạn có thể nghĩ rằng đối tượng viewport này có thể được tạo kiểu bằng CSS từ bên trong Blink, giống như phần tử DOM, ngoại trừ việc nó không thuộc DOM.

Việc này cung cấp chính xác những gì chúng tôi muốn! Chúng ta có thể áp dụng kiểu filter cho đối tượng viewport. Việc này ảnh hưởng đến quá trình kết xuất về mặt hình ảnh mà không gây cản trở cho các kiểu trang có thể quan sát hoặc DOM dưới bất kỳ hình thức nào.

Kết luận

Để tóm tắt hành trình nhỏ của mình tại đây, chúng tôi đã bắt đầu bằng cách xây dựng một nguyên mẫu sử dụng công nghệ web thay vì C++, sau đó bắt đầu di chuyển các phần của mẫu này sang Trình kết xuất Blink.

  • Trước tiên, chúng tôi tạo ra nguyên mẫu độc lập hơn bằng cách cùng dòng URL dữ liệu.
  • Sau đó, chúng tôi làm cho các URL dữ liệu nội bộ đó trở nên thân thiện với CSP, bằng cách viết cách viết hoa đặc biệt cho các URL đó.
  • Chúng tôi đã làm cho việc triển khai không phụ thuộc vào DOM và không thể ghi nhận được theo phương thức lập trình bằng cách chuyển các kiểu sang viewport Blink-nội bộ.

Điểm độc đáo của cách triển khai này là nguyên mẫu HTML/CSS/SVG của chúng tôi đã ảnh hưởng đến thiết kế kỹ thuật cuối cùng. Chúng tôi đã tìm ra cách sử dụng Nền tảng web, ngay cả trong Trình kết xuất Blink!

Để biết thêm thông tin cơ bản, hãy xem đề xuất thiết kế của chúng tôi hoặc lỗi theo dõi của Chromium có tham chiếu đến tất cả các bản vá có liên quan.

Tải các kênh xem trước xuống

Hãy cân nhắc sử dụng Chrome Canary, Dev hoặc Beta làm trình duyệt phát triển mặc định. Những kênh xem trước này cung cấp cho bạn quyền truy cập vào các tính năng mới nhất của Công cụ cho nhà phát triển, kiểm thử API nền tảng web tiên tiến và tìm ra các sự cố trên trang web của bạn trước khi người dùng làm việc đó!

Liên hệ với nhóm Công cụ của Chrome cho nhà phát triển

Hãy sử dụng các lựa chọn sau để thảo luận về các tính năng mới và thay đổi trong bài đăng hoặc bất kỳ vấn đề nào khác liên quan đến Công cụ cho nhà phát triển.

  • Gửi đề xuất hoặc phản hồi cho chúng tôi qua crbug.com.
  • Báo cáo sự cố Công cụ cho nhà phát triển bằng cách sử dụng mục Tuỳ chọn khác   Thêm   > Trợ giúp > Báo cáo sự cố Công cụ cho nhà phát triển trong Công cụ cho nhà phát triển.
  • Tweet tại @ChromeDevTools.
  • Để lại nhận xét về Video trên YouTube của chúng tôi về Tính năng mới trong Video trên YouTube của Công cụ cho nhà phát triển hoặc mẹo Công cụ cho nhà phát triển.