İçerik komut dosyaları

İçerik komut dosyaları, web sayfaları bağlamında çalışan dosyalardır. Standart Document Object Model'i (DOM) kullanarak tarayıcının ziyaret ettiği web sayfalarının ayrıntılarını okuyabilir, bu sayfalarda değişiklik yapabilir ve bilgileri üst uzantılarına iletebilirler.

İçerik komut dosyası özelliklerini anlama

İçerik komut dosyaları, aşağıdaki uzantı API'lerine doğrudan erişebilir:

İçerik komut dosyaları diğer API'lere doğrudan erişemez. Ancak uzantınızın diğer bölümleriyle mesaj alışverişi yaparak bu verilere dolaylı olarak erişebilirler.

Ayrıca, fetch() gibi API'leri kullanarak uzantınızdaki diğer dosyalara da içerik komut dosyasından erişebilirsiniz. Bunu yapmak için bu kaynakları web'de erişilebilen kaynaklar olarak bildirmeniz gerekir. Bu durumun, kaynakları aynı sitede çalışan birinci taraf veya üçüncü taraf komut dosyalarına da maruz bıraktığını unutmayın.

İzole edilmiş dünyalarda çalışma

İçerik komut dosyaları, kendi JavaScript ortamlarında çalışır. Bu sayede, sayfayla veya diğer uzantıların içerik komut dosyalarıyla çakışmadan JavaScript ortamında değişiklik yapabilirler.

Uzantılar, aşağıdaki örneğe benzer kod içeren web sayfalarında çalışabilir.

webPage.html

<html>
  <button id="mybutton">click me</button>
  <script>
    var greeting = "hello, ";
    var button = document.getElementById("mybutton");
    button.person_name = "Bob";
    button.addEventListener(
        "click", () => alert(greeting + button.person_name + "."), false);
  </script>
</html>

Bu uzantı, Komut dosyası yerleştirme bölümünde açıklanan tekniklerden birini kullanarak aşağıdaki içerik komut dosyasını yerleştirebilir.

content-script.js

var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
    "click", () => alert(greeting + button.person_name + "."), false);

Bu değişiklikle birlikte, düğme tıklandığında her iki uyarı da sırayla gösterilir.

Komut dosyası yerleştirme

İçerik komut dosyaları statik olarak tanımlanabilir, dinamik olarak tanımlanabilir veya programatik olarak eklenebilir.

Statik bildirimlerle ekleme

İyi bilinen bir dizi sayfada otomatik olarak çalıştırılması gereken komut dosyaları için manifest.json dosyasında statik içerik komut dosyası bildirimlerini kullanın.

Statik olarak beyan edilen komut dosyaları, manifest dosyasında "content_scripts" anahtarı altında kaydedilir. JavaScript dosyaları, CSS dosyaları veya her ikisi de olabilir. Otomatik olarak çalışan tüm içerik komut dosyaları eşleşme kalıplarını belirtmelidir.

manifest.json

{
 "name": "My extension",
 ...
 "content_scripts": [
   {
     "matches": ["https://*.nytimes.com/*"],
     "css": ["my-styles.css"],
     "js": ["content-script.js"]
   }
 ],
 ...
}

Ad Tür Açıklama
matches dize dizisi Zorunludur. Bu içerik komut dosyasının hangi sayfalara ekleneceğini belirtir. Bu dizelerin söz dizimi hakkında ayrıntılı bilgi için Eşleşme Kalıpları'nı, URL'leri hariç tutma hakkında bilgi için ise Eşleşme kalıpları ve globlar'ı inceleyin.
css dize dizisi İsteğe bağlıdır. Eşleşen sayfalara yerleştirilecek CSS dosyalarının listesi. Bunlar, sayfa için herhangi bir DOM oluşturulmadan veya görüntülenmeden önce bu dizide göründükleri sırayla yerleştirilir.
js dize dizisi İsteğe bağlıdır. Eşleşen sayfalara yerleştirilecek JavaScript dosyalarının listesi. Dosyalar, bu dizide göründükleri sırayla yerleştirilir. Bu listedeki her dize, uzantının kök dizinindeki bir kaynağın göreli yolunu içermelidir. Baştaki eğik çizgiler (`/`) otomatik olarak kırpılır.
run_at RunAt İsteğe bağlıdır. Komut dosyasının sayfaya ne zaman eklenmesi gerektiğini belirtir. Varsayılan olarak document_idle değerine ayarlanır.
match_about_blank boolean İsteğe bağlıdır. Komut dosyasının, üst veya açıcı çerçevenin matches içinde belirtilen kalıplardan biriyle eşleştiği bir about:blank çerçevesine yerleştirilip yerleştirilmeyeceği. Varsayılan olarak false değerine ayarlanır.
match_origin_as_fallback boolean İsteğe bağlıdır. Komut dosyasının, eşleşen bir kaynak tarafından oluşturulan ancak URL'si veya kaynağı doğrudan kalıpla eşleşmeyebilen çerçevelere yerleştirilip yerleştirilmeyeceği. Bunlar arasında about:, data:, blob: ve filesystem: gibi farklı şemalara sahip çerçeveler yer alır. Ayrıca İlgili çerçevelere yerleştirme başlıklı makaleyi de inceleyin.
world ExecutionWorld İsteğe bağlıdır. Bir komut dosyasının içinde yürütüleceği JavaScript dünyası. Varsayılan olarak ISOLATED değerine ayarlanır. Ayrıca İzole dünyalarda çalışma başlıklı makaleyi de inceleyin.

Dinamik bildirimlerle ekleme

Dinamik içerik komut dosyaları, içerik komut dosyalarının eşleşme kalıpları iyi bilinmediğinde veya içerik komut dosyaları bilinen ana makinelere her zaman eklenmemesi gerektiğinde kullanışlıdır.

Chrome 96'da kullanıma sunulan dinamik bildirimler, statik bildirimlere benzer ancak içerik komut dosyası nesnesi, manifest.json'da değil chrome.scripting ad alanındaki yöntemler kullanılarak Chrome'a kaydedilir. Scripting API, uzantı geliştiricilerin şunları yapmasına da olanak tanır:

Statik bildirimler gibi dinamik bildirimler de JavaScript dosyaları, CSS dosyaları veya her ikisini de içerebilir.

service-worker.js

chrome.scripting
  .registerContentScripts([{
    id: "session-script",
    js: ["content.js"],
    persistAcrossSessions: false,
    matches: ["*://example.com/*"],
    runAt: "document_start",
  }])
  .then(() => console.log("registration complete"))
  .catch((err) => console.warn("unexpected error", err))

service-worker.js

chrome.scripting
  .updateContentScripts([{
    id: "session-script",
    excludeMatches: ["*://admin.example.com/*"],
  }])
  .then(() => console.log("registration updated"));

service-worker.js

chrome.scripting
  .getRegisteredContentScripts()
  .then(scripts => console.log("registered content scripts", scripts));

service-worker.js

chrome.scripting
  .unregisterContentScripts({ ids: ["session-script"] })
  .then(() => console.log("un-registration complete"));

Programatik olarak ekleme

Etkinliklere yanıt olarak veya belirli durumlarda çalışması gereken içerik komut dosyaları için programatik yerleştirme kullanın.

Bir içerik komut dosyasını programatik olarak yerleştirmek için uzantınızın, komut dosyalarını yerleştirmeye çalıştığı sayfa için ana makine izinlerine sahip olması gerekir. Ana makine izinleri, uzantınızın manifestinde istenerek veya geçici olarak "activeTab" kullanılarak verilebilir.

Aşağıda, activeTab tabanlı bir uzantının farklı sürümleri verilmiştir.

manifest.json:

{
  "name": "My extension",
  ...
  "permissions": [
    "activeTab",
    "scripting"
  ],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_title": "Action Button"
  }
}

İçerik komut dosyaları dosya olarak yerleştirilebilir.

content-script.js


document.body.style.backgroundColor = "orange";

service-worker.js:

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target: { tabId: tab.id },
    files: ["content-script.js"]
  });
});

Alternatif olarak, bir işlev gövdesi içerik komut dosyası olarak yerleştirilip yürütülebilir.

service-worker.js:

function injectedFunction() {
  document.body.style.backgroundColor = "orange";
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
  });
});

Yerleştirilen işlevin, chrome.scripting.executeScript() çağrısında referans verilen işlevin bir kopyası olduğunu, orijinal işlevin kendisi olmadığını unutmayın. Bu nedenle, işlevin gövdesi bağımsız olmalıdır. İşlevin dışındaki değişkenlere yapılan referanslar, içerik komut dosyasının ReferenceError oluşturmasına neden olur.

İşlev olarak yerleştirirken işleve bağımsız değişkenler de iletebilirsiniz.

service-worker.js

function injectedFunction(color) {
  document.body.style.backgroundColor = color;
}

chrome.action.onClicked.addListener((tab) => {
  chrome.scripting.executeScript({
    target : {tabId : tab.id},
    func : injectedFunction,
    args : [ "orange" ],
  });
});

Eşleşmeleri ve globları hariç tutma

Belirtilen sayfa eşleşmesini özelleştirmek için aşağıdaki alanları bildirimli bir kayda ekleyin.

Ad Tür Açıklama
exclude_matches dize dizisi İsteğe bağlıdır. Bu içerik komut dosyasının aksi takdirde içine yerleştirileceği sayfaları hariç tutar. Bu dizelerin söz dizimi hakkında ayrıntılı bilgi için Eşleşme Kalıpları bölümüne bakın.
include_globs dize dizisi İsteğe bağlıdır. Yalnızca bu glob ile de eşleşen URL'leri içerecek şekilde matches tarihinden sonra uygulanır. Bu, @include Greasemonkey anahtar kelimesini taklit etmek için tasarlanmıştır.
exclude_globs dize dizisi İsteğe bağlıdır. Bu glob ile eşleşen URL'leri hariç tutmak için matches sonrasında uygulanır. @exclude Greasemonkey anahtar kelimesini taklit etmek için tasarlanmıştır.

Aşağıdakilerin her ikisi de doğruysa içerik komut dosyası bir sayfaya yerleştirilir:

  • URL'si herhangi bir matches kalıbı ve herhangi bir include_globs kalıbıyla eşleşir.
  • URL, exclude_matches veya exclude_globs kalıbıyla da eşleşmiyor. matches özelliği zorunlu olduğundan exclude_matches, include_globs ve exclude_globs yalnızca hangi sayfaların etkileneceğini sınırlamak için kullanılabilir.

Aşağıdaki uzantı, içerik komut dosyasını https://www.nytimes.com/health içine yerleştirir ancak https://www.nytimes.com/business içine yerleştirmez .

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  excludeMatches : [ "*://*/*business*" ],
  js : [ "contentScript.js" ],
}]);

Glob özellikleri, eşleşme kalıplarından farklı ve daha esnek bir söz dizimi kullanır. Kabul edilebilir glob dizeleri, "joker karakter" yıldız işaretleri ve soru işaretleri içerebilen URL'lerdir. Yıldız işareti (*), boş dize de dahil olmak üzere herhangi bir uzunluktaki herhangi bir dizeyle eşleşirken soru işareti (?) herhangi bir tek karakterle eşleşir.

Örneğin, https://???.example.com/foo/\* glob'u aşağıdakilerden herhangi biriyle eşleşir:

  • https://www.example.com/foo/bar
  • https://the.example.com/foo/

Ancak aşağıdaki öğelerle eşleşmez:

  • https://my.example.com/foo/bar
  • https://example.com/foo/
  • https://www.example.com/foo

Bu uzantı, içerik komut dosyasını https://www.nytimes.com/arts/index.html ve https://www.nytimes.com/jobs/index.htm* içine yerleştirir ancak https://www.nytimes.com/sports/index.html içine yerleştirmez:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Bu uzantı, içerik komut dosyasını https://history.nytimes.com ve https://.nytimes.com/history içine yerleştirir ancak https://science.nytimes.com veya https://www.nytimes.com/science içine yerleştirmez:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Doğru kapsamı elde etmek için bunlardan biri, tamamı veya bir kısmı dahil edilebilir.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "exclude_matches": ["*://*/*business*"],
      "include_globs": ["*nytimes.com/???s/*"],
      "exclude_globs": ["*science*"],
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Süre

run_at alanı, JavaScript dosyalarının web sayfasına ne zaman yerleştirileceğini kontrol eder. Tercih edilen ve varsayılan değer "document_idle". Diğer olası değerler için RunAt türüne bakın.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "run_at": "document_idle",
      "js": ["contentScript.js"]
    }
  ],
  ...
}

service-worker.js

chrome.scripting.registerContentScripts([{
  id : "test",
  matches : [ "https://*.nytimes.com/*" ],
  runAt : "document_idle",
  js : [ "contentScript.js" ],
}]);
Ad Tür Açıklama
document_idle dize Tercih edilen. Mümkün olduğunda "document_idle" kullanın.

Tarayıcı, "document_end" ile window.onload etkinliği tetiklendikten hemen sonraki bir zamanı seçerek komut dosyalarını yerleştirir. Ekleme anı, belgenin karmaşıklığına ve yüklenmesinin ne kadar sürdüğüne bağlıdır ve sayfa yükleme hızı için optimize edilmiştir.

"document_idle" konumunda çalışan içerik komut dosyalarının window.onload etkinliğini dinlemesi gerekmez. Bu komut dosyalarının, DOM tamamlandıktan sonra çalışacağı garanti edilir. Bir komut dosyasının kesinlikle window.onload'dan sonra çalışması gerekiyorsa uzantı, document.readyState özelliğini kullanarak onload'ın zaten tetiklenip tetiklenmediğini kontrol edebilir.
document_start dize Komut dosyaları, css kaynaklı dosyalardan sonra ancak başka bir DOM oluşturulmadan veya başka bir komut dosyası çalıştırılmadan önce yerleştirilir.
document_end dize Komut dosyaları, DOM tamamlandıktan hemen sonra ancak resimler ve çerçeveler gibi alt kaynaklar yüklenmeden önce yerleştirilir.

Çerçeveleri belirtme

Manifest'te belirtilen bildirimli içerik komut dosyaları için "all_frames" alanı, uzantının JavaScript ve CSS dosyalarının belirtilen URL koşullarıyla eşleşen tüm çerçevelere mi yoksa yalnızca bir sekmedeki en üstteki çerçeveye mi yerleştirileceğini belirtmesine olanak tanır:

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.nytimes.com/*"],
      "all_frames": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

chrome.scripting.registerContentScripts(...) kullanılarak içerik komut dosyaları programatik olarak kaydedilirken allFrames parametresi, içerik komut dosyasının belirtilen URL koşullarıyla eşleşen tüm çerçevelere mi yoksa yalnızca bir sekmedeki en üstteki çerçeveye mi yerleştirileceğini belirtmek için kullanılabilir. Bu yalnızca tabId ile kullanılabilir ve frameIds veya documentIds belirtilmişse kullanılamaz:

service-worker.js

chrome.scripting.registerContentScripts([{
  id: "test",
  matches : [ "https://*.nytimes.com/*" ],
  allFrames : true,
  js : [ "contentScript.js" ],
}]);

Uzantılar, eşleşen bir çerçeveyle ilişkili olan ancak kendileri eşleşmeyen çerçevelerde komut dosyaları çalıştırmak isteyebilir. Bu durumun söz konusu olduğu yaygın bir senaryo, eşleşen bir çerçeve tarafından oluşturulan ancak URL'leri komut dosyasının belirttiği kalıplarla eşleşmeyen URL'lere sahip çerçevelerdir.

Bu durum, bir uzantı about:, data:, blob: ve filesystem: düzenlerine sahip URL'lerle çerçevelere yerleştirmek istediğinde geçerlidir. Bu durumlarda URL, içerik komut dosyasının kalıbıyla eşleşmez (ve about: ile data: durumunda, üst URL'yi veya kaynağı URL'ye hiç dahil etmez, örneğin about:blank veya data:text/html,<html>Hello, World!</html>). Ancak bu çerçeveler yine de oluşturulan çerçeveyle ilişkilendirilebilir.

Uzantılar, bu çerçevelere yerleştirmek için manifest dosyasındaki içerik komut dosyası spesifikasyonunda "match_origin_as_fallback" özelliğini belirtebilir.

manifest.json

{
  "name": "My extension",
  ...
  "content_scripts": [
    {
      "matches": ["https://*.google.com/*"],
      "match_origin_as_fallback": true,
      "js": ["contentScript.js"]
    }
  ],
  ...
}

Belirtilip true olarak ayarlandığında Chrome, çerçevenin eşleşip eşleşmediğini belirlemek için çerçevenin URL'sine değil, çerçevenin başlatıcısının kaynağına bakar. Bunun, hedef çerçevenin kaynağından (ör. data: URL'lerin kaynağı boş).

Çerçevenin başlatıcısı, hedef çerçeveyi oluşturan veya hedef çerçeveye giden çerçevedir. Bu genellikle doğrudan üst veya açıcı olsa da olmayabilir (ör. bir çerçeve, bir iframe içindeki bir iframe'de gezinirken).

Bu, başlatıcı çerçevenin kaynağını karşılaştırdığı için başlatıcı çerçeve, bu kaynaktan herhangi bir yolda olabilir. Bu çıkarımı netleştirmek için Chrome, "match_origin_as_fallback" ile belirtilen tüm içerik komut dosyalarının true olarak ayarlanmasının yanı sıra * yolunu da belirtmesini zorunlu kılar.

Hem "match_origin_as_fallback" hem de "match_about_blank" belirtildiğinde "match_origin_as_fallback" öncelikli olur.

Yerleştirme sayfasıyla iletişim

İçerik komut dosyalarının ve bunları barındıran sayfaların yürütme ortamları birbirinden izole edilmiş olsa da sayfanın DOM'una erişimi paylaşırlar. Sayfa, içerik komut dosyasıyla veya içerik komut dosyası aracılığıyla uzantıyla iletişim kurmak istiyorsa bunu paylaşılan DOM üzerinden yapmalıdır.

window.postMessage() kullanılarak bir örnek verilebilir:

content-script.js

var port = chrome.runtime.connect();

window.addEventListener("message", (event) => {
  // We only accept messages from ourselves
  if (event.source !== window) {
    return;
  }

  if (event.data.type && (event.data.type === "FROM_PAGE")) {
    console.log("Content script received: " + event.data.text);
    port.postMessage(event.data.text);
  }
}, false);

example.js

document.getElementById("theButton").addEventListener("click", () => {
  window.postMessage(
      {type : "FROM_PAGE", text : "Hello from the webpage!"}, "*");
}, false);

Uzantı olmayan sayfa (ör. example.html) kendi kendine mesaj gönderir. Bu ileti, içerik komut dosyası tarafından yakalanıp incelenir ve ardından uzantı sürecine gönderilir. Bu şekilde sayfa, uzantı işlemiyle iletişim hattı oluşturur. Benzer yöntemlerle tersi de mümkündür.

Uzantı dosyalarına erişme

Bir içerik komut dosyasından uzantı dosyasına erişmek için aşağıdaki örnekte (content.js) gösterildiği gibi uzantı öğenizin mutlak URL'sini almak üzere chrome.runtime.getURL() işlevini çağırabilirsiniz:

content-script.js

let image = chrome.runtime.getURL("images/my_image.png")

Bir CSS dosyasında yazı tiplerini veya resimleri kullanmak için aşağıdaki örnekte (content.css) gösterildiği gibi bir URL oluşturmak üzere @@extension_id kullanabilirsiniz:

content.css

body {
 background-image:url('chrome-extension://__MSG_@@extension_id__/background.png');
}

@font-face {
 font-family: 'Stint Ultra Expanded';
 font-style: normal;
 font-weight: 400;
 src: url('chrome-extension://__MSG_@@extension_id__/fonts/Stint Ultra Expanded.woff') format('woff');
}

Tüm öğeler, manifest.json dosyasında web'de erişilebilen kaynaklar olarak bildirilmelidir:

manifest.json

{
 ...
 "web_accessible_resources": [
   {
     "resources": [ "images/*.png" ],
     "matches": [ "https://example.com/*" ]
   },
   {
     "resources": [ "fonts/*.woff" ],
     "matches": [ "https://example.com/*" ]
   }
 ],
 ...
}

İçerik Güvenliği Politikası

İzole edilmiş dünyalarda çalışan içerik komut dosyaları aşağıdaki İçerik Güvenliği Politikası'na (İGP) sahiptir:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Diğer uzantı bağlamlarına uygulanan kısıtlamalara benzer şekilde, bu kısıtlama eval() kullanımını ve harici komut dosyalarının yüklenmesini engeller.

Paketi açılmış uzantılar için CSP, localhost'u da içerir:

script-src 'self' 'wasm-unsafe-eval' 'inline-speculation-rules' http://localhost:* http://127.0.0.1:* chrome-extension://abcdefghijklmopqrstuvwxyz/; object-src 'self';

Ana dünyaya bir içerik komut dosyası yerleştirildiğinde sayfanın CSP'si uygulanır.

Güvende kalın

İzole edilmiş dünyalar bir koruma katmanı sağlasa da içerik komut dosyalarının kullanılması, uzantıda ve web sayfasında güvenlik açıkları oluşturabilir. İçerik komut dosyası, fetch() çağrısı gibi yöntemlerle ayrı bir web sitesinden içerik alıyorsa içeriği yerleştirmeden önce siteler arası komut dosyası çalıştırma saldırılarına karşı filtrelemeye dikkat edin. "man-in-the-middle" saldırılarını önlemek için yalnızca HTTPS üzerinden iletişim kurun.

Kötü amaçlı web sayfalarını filtrelediğinizden emin olun. Örneğin, aşağıdaki kalıplar tehlikelidir ve Manifest V3'te yasaktır:

Yapılmaması gerekenler:

content-script.js

const data = document.getElementById("json-data");
// WARNING! Might be evaluating an evil script!
const parsed = eval("(" + data + ")");
Yapılmaması gerekenler:

content-script.js

const elmt_id = ...
// WARNING! elmt_id might be '); ... evil script ... //'!
window.setTimeout("animate(" + elmt_id + ")", 200);

Bunun yerine, komut dosyası çalıştırmayan daha güvenli API'leri tercih edin:

Yapılması gerekenler:

content-script.js

const data = document.getElementById("json-data")
// JSON.parse does not evaluate the attacker's scripts.
const parsed = JSON.parse(data);
Yapılması gerekenler:

content-script.js

const elmt_id = ...
// The closure form of setTimeout does not evaluate scripts.
window.setTimeout(() => animate(elmt_id), 200);