chromium/chrome/test/data/extensions/api_test/scripting/update_scripts/worker.js

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {getInjectedElementIds, openTab} from '/_test_resources/test_util/tabs_util.js';

// Navigates to an url requested by the extension and returns the opened tab.
async function navigateToRequestedUrl() {
  const config = await chrome.test.getConfig();
  const url = `http://hostperms.com:${config.testServer.port}/simple.html`;
  let tab = await openTab(url);
  return tab;
}

chrome.test.runTests([
  async function updateScripts() {
    var scripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      excludeMatches: ['*://abc.com/*'],
      js: ['inject_element.js'],
      css: ['nothing.css'],
      runAt: 'document_end',
      allFrames: true
    }];

    var updatedScripts = [{
      id: 'inject_element_1',
      matches: ['*://hostperms.com/*'],
      excludeMatches: ['*://def.com/*'],
      js: ['inject_element_2.js'],
      allFrames: false,
      persistAcrossSessions: false
    }];

    await chrome.scripting.registerContentScripts(scripts);
    let tab = await navigateToRequestedUrl();

    // One element with id 'injected' should be injected.
    chrome.test.assertEq(['injected'], await getInjectedElementIds(tab.id));
    scripts = await chrome.scripting.getRegisteredContentScripts();
    chrome.test.assertEq(1, scripts.length);

    await chrome.scripting.updateContentScripts(updatedScripts);
    tab = await navigateToRequestedUrl();

    // After the script is updated, one element with id 'injected_2' should be
    // injected.
    chrome.test.assertEq(['injected_2'], await getInjectedElementIds(tab.id));

    const expectedScripts = [{
      id: 'inject_element_1',
      matches: ['*://hostperms.com/*'],
      excludeMatches: ['*://def.com/*'],
      js: ['inject_element_2.js'],
      css: ['nothing.css'],
      runAt: 'document_end',
      allFrames: false,
      matchOriginAsFallback: false,
      persistAcrossSessions: false,
      world: chrome.scripting.ExecutionWorld.ISOLATED
    }];

    scripts = await chrome.scripting.getRegisteredContentScripts();
    chrome.test.assertEq(expectedScripts, scripts);

    chrome.test.succeed();
  },

  // Test that updateContentScripts fails if the script ID specified does not
  // match any registered script and that the failed operation does not change
  // the current set of registered scripts.
  async function updateScriptsNonexistentId() {
    await chrome.scripting.unregisterContentScripts();
    var scripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      js: ['inject_element.js'],
      runAt: 'document_end',
    }];

    await chrome.scripting.registerContentScripts(scripts);

    const nonexistentScriptId = 'NONEXISTENT';
    var updatedScripts = [{
      id: nonexistentScriptId,
      matches: ['*://hostperms.com/*'],
      js: ['inject_element_2.js'],
      runAt: 'document_idle',
    }];

    await chrome.test.assertPromiseRejects(
        chrome.scripting.updateContentScripts(updatedScripts),
        `Error: Script with ID '${
            nonexistentScriptId}' does not exist or is not fully registered`);

    const expectedScripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      js: ['inject_element.js'],
      runAt: 'document_end',
      allFrames: false,
      matchOriginAsFallback: false,
      persistAcrossSessions: true,
      world: chrome.scripting.ExecutionWorld.ISOLATED
    }];

    scripts = await chrome.scripting.getRegisteredContentScripts();
    chrome.test.assertEq(expectedScripts, scripts);

    chrome.test.succeed();
  },

  // Test that updateContentScripts fails if more than one entry in the API call
  // contains the same script ID and that the failed operation does not change
  // the current set of registered scripts.
  async function updateScriptsDuplicateIdInAPICall() {
    await chrome.scripting.unregisterContentScripts();
    const scriptId = 'inject_element_1';
    var scripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      js: ['inject_element.js'],
      runAt: 'document_end',
    }];

    await chrome.scripting.registerContentScripts(scripts);

    var updatedScripts = [
      {
        id: scriptId,
        matches: ['*://hostperms.com/*'],
        js: ['inject_element_2.js'],
        runAt: 'document_idle',
      },
      {
        id: scriptId,
        matches: ['*://abc.com/*'],
        js: ['inject_element_2.js'],
        runAt: 'document_end',
      }
    ];

    await chrome.test.assertPromiseRejects(
        chrome.scripting.updateContentScripts(updatedScripts),
        `Error: Duplicate script ID '${scriptId}'`);

    const expectedScripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      js: ['inject_element.js'],
      runAt: 'document_end',
      allFrames: false,
      matchOriginAsFallback: false,
      persistAcrossSessions: true,
      world: chrome.scripting.ExecutionWorld.ISOLATED
    }];

    scripts = await chrome.scripting.getRegisteredContentScripts();
    chrome.test.assertEq(expectedScripts, scripts);

    chrome.test.succeed();
  },

  // Test that updateContentScripts fails if a script is specified with a file
  // that cannot be read.
  async function updateScriptsFileError() {
    await chrome.scripting.unregisterContentScripts();
    var scripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      js: ['inject_element.js'],
      runAt: 'document_end',
    }];

    await chrome.scripting.registerContentScripts(scripts);

    const scriptFile = 'NONEXISTENT.js';
    var updatedScripts = [{
      id: 'inject_element_1',
      matches: ['*://hostperms.com/*'],
      js: [scriptFile],
    }];

    await chrome.test.assertPromiseRejects(
        chrome.scripting.updateContentScripts(updatedScripts),
        `Error: Could not load javascript '${scriptFile}' for script.`);

    const expectedScripts = [{
      id: 'inject_element_1',
      matches: ['*://*/*'],
      js: ['inject_element.js'],
      runAt: 'document_end',
      allFrames: false,
      matchOriginAsFallback: false,
      persistAcrossSessions: true,
      world: chrome.scripting.ExecutionWorld.ISOLATED
    }];

    scripts = await chrome.scripting.getRegisteredContentScripts();
    chrome.test.assertEq(expectedScripts, scripts);

    chrome.test.succeed();
  },

  // Test that if two updateContentScripts calls are made in quick succession,
  // then both calls should succeed in updating their scripts and the old
  // version of these scripts are overwritten.
  // Regression for crbug.com/1454710.
  async function parallelUpdateContentScriptsCalls() {
    await chrome.scripting.unregisterContentScripts();
    var scripts = [
      {
        id: 'script_1',
        matches: ['*://*/*'],
        js: ['inject_element.js'],
        runAt: 'document_end',
        allFrames: true
      },
      {
        id: 'script_2',
        matches: ['*://*/*'],
        js: ['inject_element_2.js'],
        runAt: 'document_end',
        allFrames: true
      }
    ];

    // First, register 2 scripts that each inject a different element into the
    // page.
    await chrome.scripting.registerContentScripts(scripts);
    let tab = await navigateToRequestedUrl();

    // Both scripts should be injected, and both scripts should inject one
    // element.
    chrome.test.assertEq(
        ['injected', 'injected_2'], await getInjectedElementIds(tab.id));

    // Now update `script_1` and `script_2` to inject different elements.
    const updatedScript1 = [{
      id: 'script_1',
      matches: ['*://*/*'],
      js: ['inject_element_3.js'],
      allFrames: false,
      persistAcrossSessions: false
    }];

    const updatedScript2 = [{
      id: 'script_2',
      matches: ['*://*/*'],
      js: ['inject_element_4.js'],
      allFrames: true,
      persistAcrossSessions: false
    }];

    await Promise.allSettled([
      chrome.scripting.updateContentScripts(updatedScript1),
      chrome.scripting.updateContentScripts(updatedScript2)
    ]);

    tab = await navigateToRequestedUrl();

    // Check that the old versions of both scripts are not injected by checking
    // that the IDs of the elements injected pertain to the updated scripts.
    chrome.test.assertEq(
        ['injected_3', 'injected_4'], await getInjectedElementIds(tab.id));

    chrome.test.succeed();
  },
]);