blob: 8547391bd8262292bb5c49e1d517c8ed73c26dcf [file]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This script is run multiple times with different configurations. The content
// scripts need to determine synchronously which subtest is being run. This
// state is communicated via the URL.
const testHost = location.search.slice(1);
chrome.test.assertTrue(
testHost !== '', 'The subtest type must be specified in the query string.');
let config;
let tabId;
function getTestUrl(page) {
return `http://${testHost}:${config.testServer.port}` +
`/extensions/api_test/executescript/destructive/${page}`;
}
chrome.test.getConfig(function(retrievedConfig) {
config = retrievedConfig;
chrome.tabs.create({url: 'about:blank'}, function(tab) {
tabId = tab.id;
startTest();
});
});
function startTest() {
// These tests load a page that contains a frame, where a content script will
// be run (and remove the frame). The injection point depends on the URL:
// - ?blankstart = Load a script in the blank frame at document_start.
// - ?blankend = Load a script in the blank frame at document_end.
// - ?start = Load a script in the non-blank frame at document_start.
// - ?end = Load a script in the non-blank frame at document_end.
const kEmptyHtmlBodyPattern = /<body><\/body>/;
const allTests = [
// Empty document.
function removeHttpFrameAtDocumentStart() {
testRemoveSelf('empty_frame.html?start');
},
function removeHttpFrameAtDocumentEnd() {
testRemoveSelf('empty_frame.html?end', kEmptyHtmlBodyPattern);
},
// about:blank
function removeAboutBlankAtDocumentStart() {
testRemoveSelf('about_blank_frame.html?blankstart');
},
function removeAboutBlankAtDocumentEnd() {
testRemoveSelf('about_blank_frame.html?blankend', kEmptyHtmlBodyPattern);
},
// srcdoc frame with a HTML tag
function removeAboutSrcdocHtmlAtDocumentStart() {
testRemoveSelf('srcdoc_html_frame.html?blankstart');
},
function removeAboutSrcdocHtmlAtDocumentEnd() {
testRemoveSelf('srcdoc_html_frame.html?blankend', /<br>/);
},
// srcdoc frame with text
function removeAboutSrcdocTextOnlyAtDocumentStart() {
testRemoveSelf('srcdoc_text_frame.html?blankstart');
},
function removeAboutSrcdocTextOnlyAtDocumentEnd() {
testRemoveSelf('srcdoc_text_frame.html?blankend', /text/);
},
// <iframe></iframe> (no URL)
function removeFrameWithoutUrlAtDocumentStart() {
testRemoveSelf('no_url_frame.html?blankstart');
},
function removeFrameWithoutUrlAtDocumentEnd() {
testRemoveSelf('no_url_frame.html?blankend', kEmptyHtmlBodyPattern);
},
// An image.
function removeImageAtDocumentStart() {
testRemoveSelf('image_frame.html?start');
},
function removeImageAtDocumentEnd() {
testRemoveSelf('image_frame.html?end', /<img/);
},
// Audio (media).
function removeAudioAtDocumentStart() {
testRemoveSelf('audio_frame.html?start');
},
function removeAudioAtDocumentEnd() {
testRemoveSelf('audio_frame.html?end', /<video.+ type="audio\//);
},
// Video (media).
function removeVideoAtDocumentStart() {
testRemoveSelf('video_frame.html?start');
},
function removeVideoAtDocumentEnd() {
testRemoveSelf('video_frame.html?end', /<video.+ type="video\//);
},
// Plugins
function removePluginAtDocumentStart() {
if (maybeSkipPluginTest()) {
return;
}
testRemoveSelf('plugin_frame.html?start');
},
function removePluginAtDocumentEnd() {
if (maybeSkipPluginTest()) {
return;
}
// TODO(crbug.com/40268279): Add a way to identify that the frame is for a
// PDF.
testRemoveSelf('plugin_frame.html?end');
},
// Plain text
function removePlainTextAtDocumentStart() {
testRemoveSelf('txt_frame.html?start');
},
function removePlainTextAtDocumentEnd() {
testRemoveSelf('txt_frame.html?end', /<pre/);
},
// XHTML
function removeXhtmlAtDocumentStart() {
testRemoveSelf(
'xhtml_frame.html?start',
/<html xmlns="http:\/\/www\.w3\.org\/1999\/xhtml"><\/html>/);
},
function removeXhtmlAtDocumentEnd() {
testRemoveSelf(
'xhtml_frame.html?end',
/<html xmlns="http:\/\/www\.w3\.org\/1999\/xhtml">/);
},
// XML
function removeXmlAtDocumentStart() {
testRemoveSelf('xml_frame.html?start', /<root\/>/);
},
function removeXmlAtDocumentEnd() {
testRemoveSelf('xml_frame.html?end', /<root><child\/><\/root>/);
},
];
// Parameters defined in execute_script_apitest.cc.
const testParams = new URLSearchParams(location.hash.slice(1));
const kBucketCount = parseInt(testParams.get('bucketcount'));
const kBucketIndex = parseInt(testParams.get('bucketindex'));
const kTestsPerBucket = Math.ceil(allTests.length / kBucketCount);
chrome.test.assertTrue(
kBucketCount * kTestsPerBucket >= allTests.length,
'To cover all tests, the number of buckets multiplied by the number of ' +
'tests per bucket must be at least as big as the number of tests.');
chrome.test.assertTrue(
0 <= kBucketIndex >= 0 && kBucketIndex < kBucketCount,
'There are only ' + kBucketCount + ' buckets, so the bucket index must ' +
'be between 0 and ' + kBucketCount + ', but it was ' + kBucketIndex);
// Every run except for the last run contains |kTestsPerBucket| tests. The
// last run (i.e. |kBucketIndex| = |kBucketCount| - 1) may contain fewer tests
// if there are not enough remaining tests, since the total number of tests is
// not necessarily divisible by |kTestsPerBucket|.
let filteredTests =
allTests.slice(kBucketIndex * kTestsPerBucket).slice(0, kTestsPerBucket);
// At the document_end stage, the parser will not modify the DOM any more, so
// we can skip those tests that wait for DOM mutations to save time.
if (testHost.startsWith('dom')) {
filteredTests = filteredTests.filter(function(testfn) {
// Omit the *Documentend tests = keep the *DocumentStart tests.
return !testfn.name.endsWith('DocumentEnd');
});
}
chrome.test.runTests(filteredTests);
}
// |page| must be an existing page in this directory, and the URL must be
// matched by one content script. Otherwise the test will time out.
//
// If the regular expression |pattern| is specified, then the serialization of
// the frame content must match the pattern. This ensures that the tests are
// still testing the expected document in the future.
function testRemoveSelf(page, pattern) {
// By default, the serialization of the document must be non-empty.
const kDefaultPattern = /</;
if (page.includes('start')) {
pattern = pattern || /^<\s*html[^>]*><\/html>$/;
pattern = testHost === 'synchronous' ? pattern : kDefaultPattern;
} else if (page.includes('end')) {
pattern = pattern || kDefaultPattern;
} else {
chrome.test.fail(`URL must contain 'start' or 'end': ${page}`);
}
chrome.test.listenOnce(chrome.runtime.onMessage, function(msg, sender) {
chrome.test.assertEq(tabId, sender.tab && sender.tab.id);
chrome.test.assertEq(0, sender.frameId);
const frameHTML = msg.frameHTML;
delete msg.frameHTML;
chrome.test.assertEq({frameCount: 0}, msg);
chrome.test.assertTrue(
pattern.test(frameHTML),
`The pattern ${pattern} should be matched by: ${frameHTML}`);
});
chrome.tabs.update(tabId, {url: getTestUrl(page)});
}
// The plugin test requires a plugin to be installed. Skip the test if plugins
// are not supported.
function maybeSkipPluginTest() {
// This MIME-type should be handled by a browser plugin.
const kPluginMimeType = 'application/pdf';
for (let i = 0; i < navigator.plugins.length; ++i) {
const plugin = navigator.plugins[i];
for (let j = 0; j < plugin.length; ++j) {
const mimeType = plugin[j];
if (mimeType.type === kPluginMimeType) {
return false;
}
}
}
const kMessage = `Plugin not found for ${kPluginMimeType}, skipping test.`;
console.info(kMessage);
chrome.test.log(kMessage);
chrome.test.succeed();
return true;
}