summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html')
-rw-r--r--toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html621
1 files changed, 621 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html b/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
new file mode 100644
index 0000000000..41a58cb416
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
@@ -0,0 +1,621 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for Async Auth Prompt</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="pwmgr_common.js"></script>
+ <script type="text/javascript" src="../../../prompts/test/prompt_common.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <script class="testbody" type="text/javascript">
+ const { NetUtil } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/NetUtil.sys.mjs"
+ );
+ const { TestUtils } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+ );
+ const EXAMPLE_COM = "http://example.com/tests/toolkit/components/passwordmgr/test/mochitest/";
+ const EXAMPLE_ORG = "http://example.org/tests/toolkit/components/passwordmgr/test/mochitest/";
+ let mozproxyOrigin;
+
+ // Let prompt_common know what kind of modal type is enabled for auth prompts.
+ modalType = authPromptModalType;
+
+ // These are magically defined on the window due to the iframe IDs
+ /* global iframe1, iframe2a, iframe2b */
+
+ /**
+ * Add a listener to add some logins to be autofilled in the HTTP/proxy auth. prompts later.
+ */
+ let pwmgrParent = runInParent(() => {
+ Services.prefs.setIntPref("network.auth.subresource-http-auth-allow", 2);
+ Services.prefs.setIntPref("prompts.authentication_dialog_abuse_limit", -1);
+
+ addMessageListener("initLogins", async function onMessage(msg) {
+ const loginsData = [
+ [msg.mozproxyOrigin, "proxy_realm", "proxy_user", "proxy_pass"],
+ [msg.mozproxyOrigin, "proxy_realm2", "proxy_user2", "proxy_pass2"],
+ [msg.mozproxyOrigin, "proxy_realm3", "proxy_user3", "proxy_pass3"],
+ [msg.mozproxyOrigin, "proxy_realm4", "proxy_user4", "proxy_pass4"],
+ [msg.mozproxyOrigin, "proxy_realm5", "proxy_user5", "proxy_pass5"],
+ ["http://example.com", "mochirealm", "user1name", "user1pass"],
+ ["http://example.org", "mochirealm2", "user2name", "user2pass"],
+ ["http://example.com", "mochirealm3", "user3name", "user3pass"],
+ ["http://example.com", "mochirealm4", "user4name", "user4pass"],
+ ["http://example.com", "mochirealm5", "user5name", "user5pass"],
+ ["http://example.com", "mochirealm6", "user6name", "user6pass"]
+ ];
+ const logins = loginsData.map(([host, realm, user, pass]) => {
+ const login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+ login.init(host, null, realm, user, pass, "", "");
+ return login
+ })
+ await Services.logins.addLogins(logins);
+ });
+ }); // end runInParent
+
+ function promiseLoadedContentDoc(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", function onLoad(evt) {
+ resolve(SpecialPowers.wrap(frame).contentDocument);
+ }, { once: true });
+ });
+ }
+
+ function promiseProxyErrorLoad(frame) {
+ return TestUtils.waitForCondition(async function checkForProxyConnectFailure() {
+ try {
+ return await SpecialPowers.spawn(frame, [], function() {
+ return this.content.document.documentURI.includes("proxyConnectFailure");
+ })
+ } catch (e) {
+ // The frame may not be ready for the 'spawn' task right after setting
+ // iframe.src, which will throw an exception when that happens.
+ // Since this test is testing error load, we can't wait until the iframe
+ // is 'loaded' either. So we simply catch the exception here and retry the task
+ // later since we are in the waitForCondition loop.
+ return false;
+ }
+ }, "Waiting for proxyConnectFailure documentURI");
+ }
+
+ /**
+ * Make a channel to get the ProxyInfo used by the test harness so that we
+ * can add logins for the correct proxy origin.
+ */
+ add_task(async function setup_getProxyInfoForHarness() {
+ await new Promise(resolve => {
+ let resolveCallback = SpecialPowers.wrapCallbackObject({
+ // eslint-disable-next-line mozilla/use-chromeutils-generateqi
+ QueryInterface(iid) {
+ const interfaces = [Ci.nsIProtocolProxyCallback, Ci.nsISupports];
+
+ if (!interfaces.some(v => iid.equals(v))) {
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ }
+ return this;
+ },
+
+ onProxyAvailable(req, uri, pi, status) {
+ // Add logins using the proxy host and port used by the mochitest harness.
+ mozproxyOrigin = "moz-proxy://" + SpecialPowers.wrap(pi).host + ":" +
+ SpecialPowers.wrap(pi).port;
+
+ pwmgrParent.sendQuery("initLogins", {mozproxyOrigin}).then(resolve)
+ },
+ });
+
+ // Need to allow for arbitrary network servers defined in PAC instead of a hardcoded moz-proxy.
+ let channel = NetUtil.newChannel({
+ uri: "http://example.com",
+ loadUsingSystemPrincipal: true,
+ });
+
+ let pps = SpecialPowers.Cc["@mozilla.org/network/protocol-proxy-service;1"]
+ .getService();
+
+ pps.asyncResolve(channel, 0, resolveCallback);
+ });
+ });
+
+ add_task(async function test_proxyAuthThenTwoHTTPAuth() {
+ // Load through a single proxy with authentication required 3 different
+ // pages, first with one login, other two with their own different login.
+ // We expect to show just a single dialog for proxy authentication and
+ // then two dialogs to authenticate to login 1 and then login 2.
+
+ let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
+ let iframe2aDocPromise = promiseLoadedContentDoc(iframe2a);
+ let iframe2bDocPromise = promiseLoadedContentDoc(iframe2b);
+
+ iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
+ "r=1&" +
+ "user=user1name&" +
+ "pass=user1pass&" +
+ "realm=mochirealm&" +
+ "proxy_user=proxy_user&" +
+ "proxy_pass=proxy_pass&" +
+ "proxy_realm=proxy_realm";
+ iframe2a.src = EXAMPLE_ORG + "authenticate.sjs?" +
+ "r=2&" +
+ "user=user2name&" +
+ "pass=user2pass&" +
+ "realm=mochirealm2&" +
+ "proxy_user=proxy_user&" +
+ "proxy_pass=proxy_pass&" +
+ "proxy_realm=proxy_realm";
+ iframe2b.src = EXAMPLE_ORG + "authenticate.sjs?" +
+ "r=3&" +
+ "user=user2name&" +
+ "pass=user2pass&" +
+ "realm=mochirealm2&" +
+ "proxy_user=proxy_user&" +
+ "proxy_pass=proxy_pass&" +
+ "proxy_realm=proxy_realm";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm”`,
+ title: "Authentication Required",
+ textValue: "proxy_user",
+ passValue: "proxy_pass",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ // We don't know what order these prompts appear in so get both states and check them.
+ // We can't use Promise.all here since we can't start the 2nd timer in chromeScript.js until
+ // the first timer is done since the timer variable gets clobbered, plus we don't want
+ // different actions racing each other.
+ let promptStates = [
+ await handlePromptWithoutChecks(action),
+ await handlePromptWithoutChecks(action),
+ ];
+
+ let expected1 = Object.assign({}, state, {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
+ textValue: "user1name",
+ passValue: "user1pass",
+ });
+
+ let expected2 = Object.assign({}, state, {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.org, not the website you are currently visiting.",
+ textValue: "user2name",
+ passValue: "user2pass",
+ });
+
+ // The order isn't important.
+ let expectedPromptStates = [
+ expected1,
+ expected2,
+ ];
+
+ is(promptStates.length, expectedPromptStates.length,
+ "Check we handled the right number of prompts");
+ for (let promptState of promptStates) {
+ let expectedStateIndexForMessage = expectedPromptStates.findIndex(eps => {
+ return eps.msg == promptState.msg;
+ });
+ isnot(expectedStateIndexForMessage, -1, "Check state message was found in expected array");
+ let expectedPromptState = expectedPromptStates.splice(expectedStateIndexForMessage, 1)[0];
+ checkPromptState(promptState, expectedPromptState);
+ }
+
+ await iframe1DocPromise;
+ await iframe2aDocPromise;
+ await iframe2bDocPromise;
+
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
+ let doc = this.content.document;
+ let authok1 = doc.getElementById("ok").textContent;
+ let proxyok1 = doc.getElementById("proxy").textContent;
+ Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
+ Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
+ });
+
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 1), [], function() {
+ let doc = this.content.document;
+ let authok2a = doc.getElementById("ok").textContent;
+ let proxyok2a = doc.getElementById("proxy").textContent;
+ Assert.equal(authok2a, "PASS", "WWW Authorization OK, frame2a");
+ Assert.equal(proxyok2a, "PASS", "Proxy Authorization OK, frame2a");
+ });
+
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 2), [], function() {
+ let doc = this.content.document;
+ let authok2b = doc.getElementById("ok").textContent;
+ let proxyok2b = doc.getElementById("proxy").textContent;
+ Assert.equal(authok2b, "PASS", "WWW Authorization OK, frame2b");
+ Assert.equal(proxyok2b, "PASS", "Proxy Authorization OK, frame2b");
+ });
+ });
+
+ add_task(async function test_threeSubframesWithSameProxyAndHTTPAuth() {
+ // Load an iframe with 3 subpages all requiring the same login through
+ // an authenticated proxy. We expect 2 dialogs, proxy authentication
+ // and web authentication.
+
+ let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
+
+ iframe1.src = EXAMPLE_COM + "subtst_prompt_async.html";
+ iframe2a.src = "about:blank";
+ iframe2b.src = "about:blank";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm2”`,
+ title: "Authentication Required",
+ textValue: "proxy_user2",
+ passValue: "proxy_pass2",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ Object.assign(state, {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
+ textValue: "user3name",
+ passValue: "user3pass",
+ });
+ await handlePrompt(state, action);
+
+ await iframe1DocPromise;
+
+ function checkIframe(frameid) {
+ let doc = this.content.document;
+ let authok = doc.getElementById("ok").textContent;
+ let proxyok = doc.getElementById("proxy").textContent;
+
+ Assert.equal(authok, "PASS", "WWW Authorization OK, " + frameid);
+ Assert.equal(proxyok, "PASS", "Proxy Authorization OK, " + frameid);
+ }
+
+ let parentIFrameBC = SpecialPowers.wrap(window).windowGlobalChild
+ .browsingContext.children[0];
+
+ let childIFrame = SpecialPowers.unwrap(parentIFrameBC.children[0]);
+ await SpecialPowers.spawn(childIFrame, ["iframe1"], checkIframe);
+ childIFrame = SpecialPowers.unwrap(parentIFrameBC.children[1]);
+ await SpecialPowers.spawn(childIFrame, ["iframe2"], checkIframe);
+ childIFrame = SpecialPowers.unwrap(parentIFrameBC.children[2]);
+ await SpecialPowers.spawn(childIFrame, ["iframe3"], checkIframe);
+ });
+
+ add_task(async function test_oneFrameWithUnauthenticatedProxy() {
+ // Load in the iframe page through unauthenticated proxy
+ // and discard the proxy authentication. We expect to see
+ // unauthenticated page content and just a single dialog.
+
+ iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
+ "user=user4name&" +
+ "pass=user4pass&" +
+ "realm=mochirealm4&" +
+ "proxy_user=proxy_user3&" +
+ "proxy_pass=proxy_pass3&" +
+ "proxy_realm=proxy_realm3";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm3”`,
+ title: "Authentication Required",
+ textValue: "proxy_user3",
+ passValue: "proxy_pass3",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ await promiseProxyErrorLoad(iframe1);
+ });
+
+ add_task(async function test_reloadReusingProxyAuthButCancellingHTTPAuth() {
+ // Reload the frame from previous step and pass the proxy authentication
+ // but cancel the WWW authentication. We should get the proxy=ok and WWW=fail
+ // content as a result.
+ let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
+
+ iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
+ "user=user4name&" +
+ "pass=user4pass&" +
+ "realm=mochirealm4&" +
+ "proxy_user=proxy_user3&" +
+ "proxy_pass=proxy_pass3&" +
+ "proxy_realm=proxy_realm3";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm3”`,
+ title: "Authentication Required",
+ textValue: "proxy_user3",
+ passValue: "proxy_pass3",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ Object.assign(state, {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
+ textValue: "user4name",
+ passValue: "user4pass",
+ });
+ action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ await iframe1DocPromise;
+
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
+ let doc = this.content.document;
+ let authok1 = doc.getElementById("ok").textContent;
+ let proxyok1 = doc.getElementById("proxy").textContent;
+
+ Assert.equal(authok1, "FAIL", "WWW Authorization FAILED, frame1");
+ Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
+ });
+ });
+
+ add_task(async function test_hugePayloadCancelled() {
+ // Same as the previous two steps but let the server generate
+ // huge content load to check http channel is capable to handle
+ // case when auth dialog is canceled or accepted before unauthenticated
+ // content data is load from the server. (This would be better to
+ // implement using delay of server response).
+ iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
+ "user=user5name&" +
+ "pass=user5pass&" +
+ "realm=mochirealm5&" +
+ "proxy_user=proxy_user4&" +
+ "proxy_pass=proxy_pass4&" +
+ "proxy_realm=proxy_realm4&" +
+ "huge=1";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm4”`,
+ title: "Authentication Required",
+ textValue: "proxy_user4",
+ passValue: "proxy_pass4",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ await promiseProxyErrorLoad(iframe1);
+ });
+
+ add_task(async function test_hugeProxySuccessWWWFail() {
+ // Reload the frame from the previous step and let the proxy
+ // authentication pass but WWW fail. We expect two dialogs
+ // and an unauthenticated page content load.
+
+ let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
+ iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
+ "user=user5name&" +
+ "pass=user5pass&" +
+ "realm=mochirealm5&" +
+ "proxy_user=proxy_user4&" +
+ "proxy_pass=proxy_pass4&" +
+ "proxy_realm=proxy_realm4&" +
+ "huge=1";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm4”`,
+ title: "Authentication Required",
+ textValue: "proxy_user4",
+ passValue: "proxy_pass4",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ Object.assign(state, {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
+ textValue: "user5name",
+ passValue: "user5pass",
+ });
+ action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ await iframe1DocPromise;
+
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
+ let doc = this.content.document;
+ let authok1 = doc.getElementById("ok").textContent;
+ let proxyok1 = doc.getElementById("proxy").textContent;
+ let footnote = doc.getElementById("footnote").textContent;
+
+ Assert.equal(authok1, "FAIL", "WWW Authorization FAILED, frame1");
+ Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
+ Assert.equal(footnote, "This is a footnote after the huge content fill",
+ "Footnote present and loaded completely");
+ });
+ });
+
+ add_task(async function test_hugeProxySuccessWWWSuccess() {
+ // Reload again and let pass all authentication dialogs.
+ // Check we get the authenticated content not broken by
+ // the unauthenticated content.
+
+ let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
+ await SpecialPowers.spawn(iframe1, [], function() {
+ this.content.document.location.reload();
+ });
+
+ let state = {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
+ title: "Authentication Required",
+ textValue: "user5name",
+ passValue: "user5pass",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ await iframe1DocPromise;
+
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
+ let doc = this.content.document;
+ let authok1 = doc.getElementById("ok").textContent;
+ let proxyok1 = doc.getElementById("proxy").textContent;
+ let footnote = doc.getElementById("footnote").textContent;
+
+ Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
+ Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
+ Assert.equal(footnote, "This is a footnote after the huge content fill",
+ "Footnote present and loaded completely");
+ });
+ });
+
+ add_task(async function test_cancelSome() {
+ // Check we process all challenges sent by server when
+ // user cancels prompts
+ let iframe1DocPromise = promiseLoadedContentDoc(iframe1);
+ iframe1.src = EXAMPLE_COM + "authenticate.sjs?" +
+ "user=user6name&" +
+ "pass=user6pass&" +
+ "realm=mochirealm6&" +
+ "proxy_user=proxy_user5&" +
+ "proxy_pass=proxy_pass5&" +
+ "proxy_realm=proxy_realm5&" +
+ "huge=1&" +
+ "multiple=3";
+
+ let state = {
+ msg: `The proxy ${mozproxyOrigin} is requesting a username and password. The site says: “proxy_realm5”`,
+ title: "Authentication Required",
+ textValue: "proxy_user5",
+ passValue: "proxy_pass5",
+ iconClass: "authentication-icon question-icon",
+ titleHidden: true,
+ textHidden: false,
+ passHidden: false,
+ checkHidden: true,
+ checkMsg: "",
+ checked: false,
+ focused: "textField",
+ defButton: "button0",
+ };
+ let action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ Object.assign(state, {
+ msg: "This site is asking you to sign in. Warning: Your login information will be shared with example.com, not the website you are currently visiting.",
+ textValue: "user6name",
+ passValue: "user6pass",
+ });
+
+ action = {
+ buttonClick: "cancel",
+ };
+ await handlePrompt(state, action);
+
+ action = {
+ buttonClick: "ok",
+ };
+ await handlePrompt(state, action);
+
+ await iframe1DocPromise;
+ await SpecialPowers.spawn(getIframeBrowsingContext(window, 0), [], function() {
+ let doc = this.content.document;
+ let authok1 = doc.getElementById("ok").textContent;
+ let proxyok1 = doc.getElementById("proxy").textContent;
+ let footnote = doc.getElementById("footnote").textContent;
+
+ Assert.equal(authok1, "PASS", "WWW Authorization OK, frame1");
+ Assert.equal(proxyok1, "PASS", "Proxy Authorization OK, frame1");
+ Assert.equal(footnote, "This is a footnote after the huge content fill",
+ "Footnote present and loaded completely");
+ });
+
+ });
+ </script>
+</head>
+<body>
+ <iframe id="iframe1"></iframe>
+ <iframe id="iframe2a"></iframe>
+ <iframe id="iframe2b"></iframe>
+</body>
+</html>