ลองเขียน Chrome Extension แบบง่าย ๆ

พอดีอยากได้ฟังชั่นมาเก็บภาพจากหน้าเว็บ แต่เว็บดันบล็อกไม่ให้คลิกขวาสั่งบันทึก หรือลากรูปลงโฟลเดอร์ แต่เราก็ไม่ยอมแพ้จริงไหม? และอีกอย่างตอนนั้นใช้ Chrome เปิดอยู่เลยอยากเขียนเป็น Chrome Extension ถ้าตอนนั้นเปิดกับ Firefox ก็คงเป็น Firefox Plugin ถ้าอยู่บน Arch Linux ไม่แน่อาจจะเขียนเป็น bash script...

โอเค เข้าเรื่อง จะเขียน extension ของ Chrome ได้ จะต้องเปิด developer mode ใน Tools - Extensions ขึ้นมาก่อน ต่อไปก็สร้างโฟลเดอร์เก็บ extension ขึ้นมาสักโฟลเดอร์

เริ่มแรกเราต้องสร้างตัวไฟล์ประกาศข้อมูลของ extension ขึ้นมาก่อนโดยใช้ชื่อว่า manifest.json ที่ใช้ฟอร์แมตเป็น JSON คร่าว ๆ ก็จะมีหน้าตาแบบนี้

  1. {
  2. "manifest_version": 2,
  3. "name": "imgLoader",
  4. "description": "Download image in specified id.",
  5. "version": "0.1",
  6.  
  7. "background": {
  8. "scripts": ["extension.js"],
  9. "persistent": false
  10. },
  11.  
  12. "content_scripts": [{
  13. "matches": ["https://*/*", "http://*/*"],
  14. "js": ["content.js"]
  15. }],
  16.  
  17. "permissions": [
  18. "tabs",
  19. "downloads",
  20. "http://*/*",
  21. "https://*/*"
  22. ],
  23.  
  24. "browser_action": {
  25. "default_icon": "icon.png"
  26. }
  27. }
  • name ชื่อ extension
  • description คำอธิบายสั้น ๆ
  • version รุ่นของ extension
  • background เป็นสคริปท์หลักของ extension ในที่นี้มีอันเดียวคือ extension.js ส่วน persistent ถูกกำหนดเป็น false เพื่อให้ Chrome คืนหน่วยความจำหากไม่ได้ใช้
  • content_scripts อันนี้เป็นสคริปท์ที่ฝังลงหน้าเว็บ ถ้าไม่มีตัวนี้เราจะไม่สามารถเข้าถึงหน้าเว็บได้ (รายละเอียด)
    • matches จะให้ฝังลงหน้าไหนบ้าง (อันนี้หน้าเว็บทุกเว็บทุกหน้า — ออกแนวขี้เกียจ)
    • js ไฟล์สคริปท์ที่ใช้
  • permission ขอบเขตอำนาจว่าจะให้ extension นี้ทำอะไรได้แค่ไหน อันนี้เข้าถึงแท็บต่าง ๆ และหน้าเว็บทั้งหลาย และขออนุญาตสั่งดาวโหลดไฟล์
  • browser_action อันนี้เป็นการบอก Chrome ว่า มี default_icon เป็นรูป icon.png นะ โดยรูปที่ใช้จะต้องเป็นขนาด 19×19pixel จะสร้างโดยวิธีไหนก็ตามสะดวก แต่ต้องเผื่อว่าผู้ใช้จะเปลี่ยนธีมด้วย

เสร็จจากไฟล์ manifest.json แล้ว ต่อมาก็สร้างไฟล์ extension.js กับ content.js เปล่า ๆ ขึ้นมาอีกไฟล์ แล้วกลับไปที่ Chrome หน้า extension คลิกที่ปุ่ม Load unpacked extension แล้วเลือกโฟลเดอร์ที่เราสร้างเมื่อกี้ดู ถ้าหากไม่มีอะไรผิดพลาดจะมีไอคอน extension ของเราโผล่ขึ้นมาที่ toolbar แต่ถ้ามีข้อผิดพลาดก็แก้ตามที่มันบอก

มาดูที่ไฟล์ content.js บ้าง ก่อนอื่นต้องนึกก่อนว่า เราต้องการทำอะไรบนหน้าเว็บ ทำอย่างไร และผลเป็นอย่างไร สำหรับผม ผมต้องการจะรู้ว่า URL ของรูปภาพเป็นอะไร โดยที่ผมรู้ id ของแท็ก <img> อยู่แล้วก็เขียนฟังชั่นขึ้นมาใน content.js ก่อน

  1. function getImageUrl(id) {
  2. var imgTag = document.getElementById(id);
  3. return imgTag.attributes['src'].value;
  4. }

ต่อมาก็สร้าง listener สำหรับดักฟัง (onMessage) สิ่งที่ extension ของเราจะส่งมาให้กับหน้าเว็บ แล้วส่ง URL ของภาพกลับไปยัง extension อีกที

  1. chrome.runtime.onMessage.addListener(function(data, sender, replyBack) {
  2. var imageUrl = '';
  3.  
  4. if (data.id != null) {
  5. imageUrl = getImageUrl(data.id);
  6. replyBack({ url: imageUrl });
  7. }
  8. });

มาดูไฟล์ extension.js บ้าง สำหรับของผม ผมจะให้ extension ทำงานเฉพาะเมื่อคลิก และทำงานบนแท็บปัจจุบันเท่านั้น ก็จะเรียกใช้ฟังชั่น onClicked เพื่อให้ทำงานเวลาเราคลิกบนปุ่ม แล้วใช้ chrome.tabs.sendMessage() ส่งข้อมูลพร้อมกับ id ของแท็บไปยัง content.js เมื่อ content.js ตอบกลับมาก็จะเรียก callback function ชื่อ _processResult (จริง ๆ เขียนเป็น expression ก็ได้นะ แต่ที่แยก เพราะอธิบายง่ายกว่า)

สิ่งที่ extension ส่งไปคือ id ของแท็ก <img> ที่ผมต้องการจะหา URL ของภาพ สังเกตว่าจะใช้ chrome.tabs เพราะส่งข้อมูลไปยัง content script แต่ถ้าส่งไปยัง extension script จะใช้ chrome.runtime แทน

  1. var log = true;
  2. var imgId = "dispImg";
  3.  
  4. chrome.browserAction.onClicked.addListener(function(tab) {
  5. chrome.tabs.sendMessage(tab.id, { id: imgId }, _processResult);
  6. });

มาดูฟังชั่น _processResult() กันบ้าง ฟังชั่นนี้ก็นำข้อมูลที่ content.js ตอบกลับมาประมวลผล ซึ่งคำสั่งที่สำคัญจริง ๆ มีคำสั่งเดียวคือ chrome.downloads.download() เพื่อสั่งให้ Chrome ดาวโหลดไฟล์นั่นเอง API อันนี้พึ่งเพิ่มเข้าในในรุ่น 31 stable ถือว่าใหม่ สด ๆ ร้อน ๆ (สำหรับชาว stable) แต่จะใช้ API นี้ได้ต้องขออนุญาตดาวโหลดไฟล์ใน manifest.json ด้วย

  1. function _processResult(result) {
  2. if (result == null) {
  3. if (log) console.log('listener not found; check manifest.json or reload current page and then try again');
  4. return;
  5. }
  6.  
  7. if (log) console.log('image url: ' + result.url);
  8.  
  9. chrome.downloads.download({ url: result.url });
  10. }

เมื่อเสร็จแล้วให้กลับไปหน้า Extension ของ Chrome แล้วคลิก Reload ในส่วนของ extension ของเราอีกครั้ง แล้วลองเข้าหน้าเว็บที่มีแท็ก <img> ใช้ id ตามที่กำหนด (อันนี้คือ dispImg) แต่ถ้าเข้าหน้านั้นรออยู่แล้วต้อง refresh หน้านั้นใหม่ก่อน พอกดปุ่ม extension ของเรา ก็จะมีหน้าต่างให้บันทึกไฟล์ขึ้นมาก็แสดงว่า extension ทำงานได้ถูกต้อง

แต่ถ้ามันผิดล่ะ จะตรวจสอบ (debug) ยังไง?

สำหรับไฟล์ content.js นั้นเนื่องจากมันฝังอยู่บนหน้าเว็บนั้น ๆ ก็เรียกดู JavaScript console ของหน้านั้น ๆ (Tools - JavaScript Console) แต่สำหรับ extension.js ต้องเรียกจากหน้า Extension ตรงส่วน Inspect views: background_page แทน ที่แยกกันเพราะ ทำงานคนละส่วนกัน

แล้วทำไมต้องมีไฟล์สคริปท์ถึง 2 ไฟล์ ไฟล์เดียวได้ไหม?

อันที่จริง เราสามารถมีไฟล์ extension.js หรือ content.js ไฟล์ใดไฟล์หนึ่งก็ได้ แต่ก็ขึ้นอยู่กับว่า extension ที่เราเขียนมันทำงานยังไง เพราะว่า extension.js ที่กำหนดใน background ใน manifest.json จะถูกจำกัดไม่ให้เข้าถึง DOM ของหน้าเว็บต่าง ๆ ในขณะที่ content.js ที่กำหนดใน content_scripts ใน manifest.json ก็ถูกจำกัดไม่ให้เข้าถึง API ต่าง ๆ ของ Chrome ซึ่งถ้าหากต้องการเข้าถึงทั้ง 2 ส่วนก็ต้องเขียนทั้ง 2 ไฟล์ แล้วก็ส่งข้อมูลถึงกันด้วยระบบ message ซึ่งอันนี้เป็น simple message คือส่งครั้งเดียวแล้วจบ ถ้าต้องการมากกว่านั้นลองอ่านดูจาก Message Passing

ถือว่าไม่ยากนัก ถ้าหากเขียน JavaScript เป็น แต่สำหรับผม อยากจะฝัง jQuery ลงไปด้วย และอีกอย่าง กว่าจะหาวิธีส่ง message ระหว่างกันก็หมดไปครึ่งวัน... เน็ตหลุดไป 2 รอบ ที่แย่ยิ่งกว่าคือ พอค่ำโทรไปไม่มีคนรับสาย ToT

ไฟล์ content.js

  1. function getImageUrl(id) {
  2. var imgTag = document.getElementById(id);
  3. return imgTag.attributes['src'].value;
  4. }
  5.  
  6. chrome.runtime.onMessage.addListener(function(data, sender, replyBack) {
  7. var id = data.id;
  8. var imageUrl = '';
  9.  
  10. if (data.id != null) {
  11. imageUrl = getImageUrl(data.id);
  12. replyBack({ url: imageUrl });
  13. }
  14. });

ไฟล์ extension.js

  1. var log = true;
  2. var imgId = "dispImg";
  3.  
  4. function _processResult(result) {
  5. if (result == null) {
  6. if (log) console.log('listener not found; check manifest or reload current page and then try again');
  7. return;
  8. }
  9.  
  10. if (log) console.log('image url: ' + result.url);
  11.  
  12. chrome.downloads.download({ url: result.url });
  13. }
  14.  
  15. chrome.browserAction.onClicked.addListener(function(tab) {
  16. if (log) console.log("find image on tab " + tab.id);
  17.  
  18. chrome.tabs.sendMessage(tab.id, { id: imgId }, _processResult);
  19. });

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.