blob: 1472a5e9c1a8dfc413d31c551db0a145d7b5ec3b [file] [log] [blame]
<html>
<!-- This page can create whatever iframe structure you want, across whatever
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 src="http://b.com:1234/cross_site_iframe_factory.html?b(c(),d())">
Inside of which, then, are created the two leaf iframes:
<iframe src="http://c.com:1234/cross_site_iframe_factory.html?c()">
<iframe 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 src="http://a.com:1234/cross_site_iframe_factory.html?b()" allowfullscreen>
<iframe src="http://c.com:1234/cross_site_iframe_factory.html?c{sandbox-allow-scripts}(d())" sandbox="allow-scripts">
These options are supported:
allowfullscreen
allowpaymentrequest
allow-{featurename} - Adds {featurename} to the "allow" attribute
sandbox-{flagname} - Adds {flagname} to the "sandbox" attribute
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) {
// Map the first character to an index. This could be negative, we don't
// really care.
var index = seedString.charCodeAt(0) - 'a'.charCodeAt(0);
// If the first character is 'a', this will the the starting color.
var hueOfA = 200; // Spoiler alert: it's blue.
// Color palette generation articles suggest that spinning the hue wheel by
// the golden ratio yields a magically nice color distribution. Something
// about sunflower seeds. I am skeptical of the rigor of that claim (probably
// any irrational number at a slight offset from 2/3 would do) but it does
// look pretty.
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) {
// Light pastel.
return pastelColorForFirstCharacter(site, .75);
}
function borderColorForSite(site) {
// Darker color in the same hue has the background.
return pastelColorForFirstCharacter(site, .32);
}
/**
* 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.
*/
function canonicalizeSite(siteString) {
if (siteString.indexOf('.') == -1)
return siteString + '.com';
return siteString;
}
/**
* Parses the list of iframe options and applies them to the provided element.
*/
function applyIFrameOptions(element, options) {
var sandboxArguments = [];
var allowFeatures = [];
for (var option of options) {
if (option == "allowfullscreen") {
element.allowFullscreen = true;
}
if (option == "allowpaymentrequest") {
element.allowPaymentRequest = true;
}
if (option.startsWith("sandbox-")) {
sandboxArguments.push(option.slice(8));
}
if (option.startsWith("allow-")) {
allowFeatures.push(option.slice(6))
}
}
if (sandboxArguments.length) {
element.sandbox = sandboxArguments.join(" ");
}
if (allowFeatures.length) {
element.allow = allowFeatures.join(" ");
}
}
/**
* Simple recursive layout heuristic, since frames can't size themselves.
* This scribbles .layoutX and .layoutY properties into |tree|.
*/
function layout(tree) {
// Step 1: layout children.
var numFrames = tree.children.length;
for (var i = 0; i < numFrames; i++) {
layout(tree.children[i]);
}
// Step 2: find largest child.
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);
}
// Step 3: Tweakable control parameters.
var minX = 110; // Should be wide enough to fit a decent sized domain.
var minY = 110; // Could be less, but squares look nice.
var extraYPerLevel = 50; // Needs to be tall enough to fit a line of text.
var extraXPerLevel = 50; // Could be less, but squares look nice.
// Account for padding around each <iframe>.
largestChildX += 30;
largestChildY += 30;
// Step 4: Assume a gridSizeX-by-gridSizeY layout that's big enough to fit if
// all children were the size of the largest one.
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 = canonicalizeSite(frameTree.value);
// Apply style to the current document.
document.getElementById('siteNameHeading').appendChild(
document.createTextNode(currentSite));
document.body.style.backgroundColor = backgroundColorForSite(currentSite);
// Determine how big the children should be (using a very rough heuristic).
layout(frameTree);
for (var i = 0; i < frameTree.children.length; i++) {
// Compute the URL for this iframe.
var site = canonicalizeSite(frameTree.children[i].value);
var subtreeString = TreeParserUtil.flatten(frameTree.children[i]);
var url = '';
url += window.location.protocol + '//'; // scheme (preserved)
url += goCrossSite ? site : window.location.host; // host
if (window.location.port)
url += ':' + window.location.port; // port (preserved)
url += window.location.pathname; // path (preserved)
url += '?' + encodeURIComponent(subtreeString); // query
// Construct the iframe.
var iframe = document.createElement('iframe');
iframe.src = url;
iframe.id = "child-" + i;
iframe.style.borderColor = borderColorForSite(site);
iframe.width = frameTree.children[i].layoutX;
iframe.height = frameTree.children[i].layoutY;
// Apply any additional attributes.
applyIFrameOptions(iframe, frameTree.children[i].attributes);
// Add the iframe to the document.
document.body.appendChild(iframe);
}
}
main();
</script>
</body></html>