<html>
sites you want. This is useful for testing site isolation.
Example usage in a browsertest, explained:
GURL url = embedded_test_server()->
GetURL("a.com", "/cross_site_iframe_factory.html?a(b(c,d))");
When you navigate to the above URL, the outer document (on a.com) will create a
single iframe:
<iframe id="child-0" src="http://b.com:1234/cross_site_iframe_factory.html?b(c(),d())">
Inside of which, then, are created the two leaf iframes:
<iframe id="child-0" src="http://c.com:1234/cross_site_iframe_factory.html?c()">
<iframe id="child-1" src="http://d.com:1234/cross_site_iframe_factory.html?d()">
Add iframe options by enclosing them in '{' and '}' characters after the
hostname (multiple options can be separated with commas):
cross_site_iframe_factory.html?a(b{allowfullscreen}(),c{sandbox-allow-scripts}(d))
Will create two iframes:
<iframe id="child-0" src="http://a.com:1234/cross_site_iframe_factory.html?b()" allowfullscreen>
<iframe id="child-1" src="http://c.com:1234/cross_site_iframe_factory.html?c{sandbox-allow-scripts}(d())" sandbox="allow-scripts">
To specify the site for each iframe, you can use a simple identifier like "a"
or "b", and ".com" will be automatically appended. You can also specify a port
number to use for each site by using a label like "a:12345". If no port number
is given, the port will default to the port number of the top level frame.
You can also specify an arbitrary full URL for any bottommost leaf frame.
These options are supported:
allowfullscreen
allowpaymentrequest
allow-{featurename} - Adds {featurename} to the "allow" attribute
sandbox-{flagname} - Adds {flagname} to the "sandbox" attribute
with-shared-worker - Creates a shared worker in each frame.
with-worker - Creates a dedicated worker in each frame.
To make this page work, your browsertest needs a MockHostResolver, like:
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
You can play around with the arguments by loading this page via file://, but
you probably won't get the same process behavior as if you loaded via http. -->
<head>
<title>Cross-site iframe factory</title>
<style>
body {
font-family: "DejaVu Sans", Sans-Serif;
text-align: center;
}
iframe {
border-radius: 7px;
border-style: solid;
vertical-align: top;
margin: 2px;
box-shadow: 2px 2px 2px #888888;
}
</style>
</head>
<body>
<h2 id='siteNameHeading'></h2>
<script src='tree_parser_util.js'></script>
<script type='text/javascript'>
* Determines a random pastel-ish color from the first character of a string.
*/
function pastelColorForFirstCharacter(seedString, lightness) {
var index = seedString.charCodeAt(0) - 'a'.charCodeAt(0);
var hueOfA = 200;
var phi = 2 / (1 + Math.pow(5, .5));
var hue = Math.round((360 * index * phi + hueOfA) % 360);
return 'hsl(' + hue + ', 60%, ' + Math.round(100 * lightness) + '%)';
}
function backgroundColorForSite(site) {
return pastelColorForFirstCharacter(site, .75);
}
function borderColorForSite(site) {
return pastelColorForFirstCharacter(site, .32);
}
function isUrl(siteString) {
try {
var url = new URL(siteString);
} catch (e) {
if (e instanceof TypeError)
return false;
}
return true;
}
* Extract the specified port number, if any. Returns empty string if not
* specified.
*/
function sitePortNumber(siteString) {
let index = siteString.indexOf(':');
if (index == -1)
return ""
return siteString.substring(index + 1);
}
* Adds ".com" to an argument if it doesn't already have a top level domain.
* This cuts down on noise in the query string, letting you use single-letter
* names. Adds the specified port number, if any, or the default port otherwise.
*/
function canonicalizeSiteAndPort(siteString, defaultPort) {
var portNumber = sitePortNumber(siteString) || defaultPort;
var hostName = siteString.split(':')[0];
if (hostName !== "localhost" && hostName.indexOf('.') == -1)
hostName = hostName + '.com';
return hostName + (portNumber ? ':' + portNumber : "");
}
* Parses the list of iframe options and applies them to the provided element.
*/
function applyIFrameOptions(element, options) {
var sandbox = false;
var sandboxArguments = [];
var allowFeatures = [];
for (var option of options) {
if (option == "allowfullscreen") {
element.allowFullscreen = true;
}
if (option == "allowpaymentrequest") {
element.allowPaymentRequest = true;
}
if (option == "sandbox") {
sandbox = true;
}
if (option.startsWith("sandbox-")) {
sandbox = true;
sandboxArguments.push(option.slice(8));
}
if (option.startsWith("allow-")) {
allowFeatures.push(option.slice(6))
}
}
if (sandbox) {
element.sandbox = sandboxArguments.join(" ");
}
if (allowFeatures.length) {
element.allow = allowFeatures.join(" ");
}
}
* Determines whether or not text should be rendered by checking whether
* "no-text-render" is among the list of attributes.
*/
function shouldRenderText(attributes) {
for (var attribute of attributes) {
if (attribute == "no-text-render") {
return false;
}
}
return true;
}
* Determines whether or not the frame should be positioned outside of
* its parent frame by checking whether "out-of-view" is among the list
* of attributes.
*/
function renderOutsideParentView(attributes) {
for (var attribute of attributes) {
if (attribute == "out-of-view") {
return true;
}
}
return false;
}
* Determines whether or not the frame should create a worker by checking
* whether "with-worker" is among the list of attributes.
*/
function shouldCreateWorker(attributes) {
for (var attribute of attributes) {
if (attribute == "with-worker") {
return true;
}
}
return false;
}
* Determines whether or not the frame should create a shared worker by checking
* whether "with-shared-worker" is among the list of attributes.
*/
function shouldCreateSharedWorker(attributes) {
for (var attribute of attributes) {
if (attribute == "with-shared-worker") {
return true;
}
}
return false;
}
* Simple recursive layout heuristic, since frames can't size themselves.
* This scribbles .layoutX and .layoutY properties into |tree|.
*/
function layout(tree) {
var numFrames = tree.children.length;
for (var i = 0; i < numFrames; i++) {
layout(tree.children[i]);
}
var largestChildX = 0;
var largestChildY = 0;
for (var i = 0; i < numFrames; i++) {
largestChildX = Math.max(largestChildX, tree.children[i].layoutX);
largestChildY = Math.max(largestChildY, tree.children[i].layoutY);
}
var minX = 110;
var minY = 110;
var extraYPerLevel = 50;
var extraXPerLevel = 50;
largestChildX += 30;
largestChildY += 30;
var gridSizeX = Math.ceil(Math.sqrt(numFrames));
var gridSizeY = Math.round(Math.sqrt(numFrames));
tree.layoutX = Math.max(gridSizeX * largestChildX + extraXPerLevel, minX);
tree.layoutY = Math.max(gridSizeY * largestChildY + extraYPerLevel, minY);
}
function main() {
var goCrossSite = !window.location.protocol.startsWith('file');
var queryString = decodeURIComponent(window.location.search.substring(1));
var frameTree = TreeParserUtil.parse(queryString);
var currentSite = isUrl(frameTree.value)
? frameTree.value
: canonicalizeSiteAndPort(frameTree.value, "");
if (currentSite != window.location.hostname) {
console.error(`The first hostname in the query-string does not match the` +
` hostname of the URL: ${currentSite} != ${window.location.hostname}`);
return;
}
if (shouldRenderText(frameTree.attributes)) {
document.getElementById('siteNameHeading').appendChild(
document.createTextNode(currentSite));
}
if (shouldCreateWorker(frameTree.attributes)) {
let worker_url = 'workers/empty.js';
worker_url += '?' + queryString;
console.log(worker_url);
self.worker = new Worker(worker_url);
}
if (shouldCreateSharedWorker(frameTree.attributes)) {
let worker_url = 'workers/empty.js';
worker_url += '?' + queryString;
console.log(worker_url);
self.worker = new SharedWorker(worker_url);
}
document.body.style.backgroundColor = backgroundColorForSite(currentSite);
layout(frameTree);
for (var i = 0; i < frameTree.children.length; i++) {
let url = frameTree.children[i].value;
let siteAndPort = url;
if (!isUrl(url)) {
siteAndPort = canonicalizeSiteAndPort(url, window.location.port);
const subtreeString = TreeParserUtil.flatten(frameTree.children[i]);
url = '';
url += window.location.protocol + '//';
url += goCrossSite ? siteAndPort : window.location.host;
url += window.location.pathname;
url += '?' + encodeURIComponent(subtreeString);
}
var iframe = document.createElement('iframe');
iframe.src = url;
iframe.id = "child-" + i;
iframe.style.borderColor = borderColorForSite(siteAndPort);
iframe.width = frameTree.children[i].layoutX;
iframe.height = frameTree.children[i].layoutY;
if (renderOutsideParentView(frameTree.children[i].attributes)) {
iframe.style.position = "fixed";
iframe.style.top = "150%";
iframe.style.left = "150%";
}
applyIFrameOptions(iframe, frameTree.children[i].attributes);
document.body.appendChild(iframe);
}
}
main();
</script>
</body></html>