diff options
Diffstat (limited to 'browser/base/content/test/outOfProcess')
9 files changed, 662 insertions, 0 deletions
diff --git a/browser/base/content/test/outOfProcess/browser.ini b/browser/base/content/test/outOfProcess/browser.ini new file mode 100644 index 0000000000..de8ad0cb8b --- /dev/null +++ b/browser/base/content/test/outOfProcess/browser.ini @@ -0,0 +1,15 @@ +[DEFAULT] +support-files = + file_base.html + file_frame1.html + file_frame2.html + file_innerframe.html + head.js + +[browser_basic_outofprocess.js] +[browser_controller.js] +skip-if = + os == "linux" && bits == 64 # Bug 1663506 + os == "mac" && debug # Bug 1663506 + os == "win" && bits == 64 # Bug 1663506 +[browser_promisefocus.js] diff --git a/browser/base/content/test/outOfProcess/browser_basic_outofprocess.js b/browser/base/content/test/outOfProcess/browser_basic_outofprocess.js new file mode 100644 index 0000000000..50914a286c --- /dev/null +++ b/browser/base/content/test/outOfProcess/browser_basic_outofprocess.js @@ -0,0 +1,149 @@ +/** + * Verify that the colors were set properly. This has the effect of + * verifying that the processes are assigned for child frames correctly. + */ +async function verifyBaseFrameStructure( + browsingContexts, + testname, + expectedHTML +) { + function checkColorAndText(bc, desc, expectedColor, expectedText) { + return SpecialPowers.spawn( + bc, + [expectedColor, expectedText, desc], + (expectedColorChild, expectedTextChild, descChild) => { + Assert.equal( + content.document.documentElement.style.backgroundColor, + expectedColorChild, + descChild + " color" + ); + Assert.equal( + content.document.getElementById("insertPoint").innerHTML, + expectedTextChild, + descChild + " text" + ); + } + ); + } + + let useOOPFrames = gFissionBrowser; + + is( + browsingContexts.length, + TOTAL_FRAME_COUNT, + "correct number of browsing contexts" + ); + await checkColorAndText( + browsingContexts[0], + testname + " base", + "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[1], + testname + " frame 1", + useOOPFrames ? "seashell" : "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[2], + testname + " frame 1-1", + useOOPFrames ? "seashell" : "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[3], + testname + " frame 2", + useOOPFrames ? "lightcyan" : "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[4], + testname + " frame 2-1", + useOOPFrames ? "seashell" : "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[5], + testname + " frame 2-2", + useOOPFrames ? "lightcyan" : "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[6], + testname + " frame 2-3", + useOOPFrames ? "palegreen" : "white", + expectedHTML.next().value + ); + await checkColorAndText( + browsingContexts[7], + testname + " frame 2-4", + "white", + expectedHTML.next().value + ); +} + +/** + * Test setting up all of the frames where a string of markup is passed + * to initChildFrames. + */ +add_task(async function test_subframes_string() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + OOP_BASE_PAGE_URI + ); + + const markup = "<p>Text</p>"; + + let browser = tab.linkedBrowser; + let browsingContexts = await initChildFrames(browser, markup); + + function* getExpectedHTML() { + for (let c = 1; c <= TOTAL_FRAME_COUNT; c++) { + yield markup; + } + ok(false, "Frame count does not match actual number of frames"); + } + await verifyBaseFrameStructure(browsingContexts, "string", getExpectedHTML()); + + BrowserTestUtils.removeTab(tab); +}); + +/** + * Test setting up all of the frames where a function that returns different markup + * is passed to initChildFrames. + */ +add_task(async function test_subframes_function() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + OOP_BASE_PAGE_URI + ); + let browser = tab.linkedBrowser; + + let counter = 0; + let browsingContexts = await initChildFrames( + browser, + function (browsingContext) { + return "<p>Text " + ++counter + "</p>"; + } + ); + + is( + counter, + TOTAL_FRAME_COUNT, + "insert HTML function called the correct number of times" + ); + + function* getExpectedHTML() { + for (let c = 1; c <= TOTAL_FRAME_COUNT; c++) { + yield "<p>Text " + c + "</p>"; + } + } + await verifyBaseFrameStructure( + browsingContexts, + "function", + getExpectedHTML() + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/outOfProcess/browser_controller.js b/browser/base/content/test/outOfProcess/browser_controller.js new file mode 100644 index 0000000000..f9d9ca8c93 --- /dev/null +++ b/browser/base/content/test/outOfProcess/browser_controller.js @@ -0,0 +1,127 @@ +function checkCommandState(testid, undoEnabled, copyEnabled, deleteEnabled) { + is( + !document.getElementById("cmd_undo").hasAttribute("disabled"), + undoEnabled, + testid + " undo" + ); + is( + !document.getElementById("cmd_copy").hasAttribute("disabled"), + copyEnabled, + testid + " copy" + ); + is( + !document.getElementById("cmd_delete").hasAttribute("disabled"), + deleteEnabled, + testid + " delete" + ); +} + +function keyAndUpdate(key, eventDetails, updateEventsCount) { + let updatePromise = BrowserTestUtils.waitForEvent( + window, + "commandupdate", + false, + () => { + return --updateEventsCount == 0; + } + ); + EventUtils.synthesizeKey(key, eventDetails); + return updatePromise; +} + +add_task(async function test_controllers_subframes() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + OOP_BASE_PAGE_URI + ); + let browser = tab.linkedBrowser; + let browsingContexts = await initChildFrames( + browser, + "<input id='input'><br><br>" + ); + + gURLBar.focus(); + + for (let stepNum = 0; stepNum < browsingContexts.length; stepNum++) { + await keyAndUpdate(stepNum > 0 ? "VK_TAB" : "VK_F6", {}, 6); + + // Since focus may be switching into a separate process here, + // need to wait for the focus to have been updated. + await SpecialPowers.spawn(browsingContexts[stepNum], [], () => { + return ContentTaskUtils.waitForCondition( + () => content.browsingContext.isActive && content.document.hasFocus() + ); + }); + + // Force the UI to update on platforms that don't + // normally do so until menus are opened. + if (AppConstants.platform != "macosx") { + goUpdateGlobalEditMenuItems(true); + } + + await SpecialPowers.spawn(browsingContexts[stepNum], [], () => { + // Both the tab key and document navigation with F6 will focus + // the root of the document within the frame. + let document = content.document; + Assert.equal( + document.activeElement, + document.documentElement, + "root focused" + ); + }); + // XXX Currently, Copy is always enabled when the root (not an editor element) + // is focused. Possibly that should only be true if a listener is present? + checkCommandState("step " + stepNum + " root focused", false, true, false); + + // Tab to the textbox. + await keyAndUpdate("VK_TAB", {}, 1); + + if (AppConstants.platform != "macosx") { + goUpdateGlobalEditMenuItems(true); + } + + await SpecialPowers.spawn(browsingContexts[stepNum], [], () => { + Assert.equal( + content.document.activeElement, + content.document.getElementById("input"), + "input focused" + ); + }); + checkCommandState( + "step " + stepNum + " input focused", + false, + false, + false + ); + + // Type into the textbox. + await keyAndUpdate("a", {}, 1); + checkCommandState("step " + stepNum + " typed", true, false, false); + + await SpecialPowers.spawn(browsingContexts[stepNum], [], () => { + Assert.equal( + content.document.activeElement, + content.document.getElementById("input"), + "input focused" + ); + }); + + // Select all text; this causes the Copy and Delete commands to be enabled. + await keyAndUpdate("a", { accelKey: true }, 1); + if (AppConstants.platform != "macosx") { + goUpdateGlobalEditMenuItems(true); + } + + checkCommandState("step " + stepNum + " selected", true, true, true); + + // Now make sure that the text is selected. + await SpecialPowers.spawn(browsingContexts[stepNum], [], () => { + let input = content.document.getElementById("input"); + Assert.equal(input.value, "a", "text matches"); + Assert.equal(input.selectionStart, 0, "selectionStart matches"); + Assert.equal(input.selectionEnd, 1, "selectionEnd matches"); + }); + } + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/outOfProcess/browser_promisefocus.js b/browser/base/content/test/outOfProcess/browser_promisefocus.js new file mode 100644 index 0000000000..9018c0f0ae --- /dev/null +++ b/browser/base/content/test/outOfProcess/browser_promisefocus.js @@ -0,0 +1,262 @@ +// Opens another window and switches focus between them. +add_task(async function test_window_focus() { + let window2 = await BrowserTestUtils.openNewBrowserWindow(); + ok(!document.hasFocus(), "hasFocus after open second window"); + ok(window2.document.hasFocus(), "hasFocus after open second window"); + is( + Services.focus.activeWindow, + window2, + "activeWindow after open second window" + ); + is( + Services.focus.focusedWindow, + window2, + "focusedWindow after open second window" + ); + + await SimpleTest.promiseFocus(window); + ok(document.hasFocus(), "hasFocus after promiseFocus on window"); + ok(!window2.document.hasFocus(), "hasFocus after promiseFocus on window"); + is( + Services.focus.activeWindow, + window, + "activeWindow after promiseFocus on window" + ); + is( + Services.focus.focusedWindow, + window, + "focusedWindow after promiseFocus on window" + ); + + await SimpleTest.promiseFocus(window2); + ok(!document.hasFocus(), "hasFocus after promiseFocus on second window"); + ok( + window2.document.hasFocus(), + "hasFocus after promiseFocus on second window" + ); + is( + Services.focus.activeWindow, + window2, + "activeWindow after promiseFocus on second window" + ); + is( + Services.focus.focusedWindow, + window2, + "focusedWindow after promiseFocus on second window" + ); + + await BrowserTestUtils.closeWindow(window2); + + // If the window is already focused, this should just return. + await SimpleTest.promiseFocus(window); + await SimpleTest.promiseFocus(window); +}); + +// Opens two tabs and ensures that focus can be switched to the browser. +add_task(async function test_tab_focus() { + let tab1 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<input>" + ); + + let tab2 = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + "data:text/html,<input>" + ); + + gURLBar.focus(); + + await SimpleTest.promiseFocus(tab2.linkedBrowser); + is( + document.activeElement, + tab2.linkedBrowser, + "Browser is focused after promiseFocus" + ); + + await SpecialPowers.spawn(tab1.linkedBrowser, [], () => { + Assert.equal( + Services.focus.activeBrowsingContext, + null, + "activeBrowsingContext in child process in hidden tab" + ); + Assert.equal( + Services.focus.focusedWindow, + null, + "focusedWindow in child process in hidden tab" + ); + Assert.ok( + !content.document.hasFocus(), + "hasFocus in child process in hidden tab" + ); + }); + + await SpecialPowers.spawn(tab2.linkedBrowser, [], () => { + Assert.equal( + Services.focus.activeBrowsingContext, + content.browsingContext, + "activeBrowsingContext in child process in visible tab" + ); + Assert.equal( + Services.focus.focusedWindow, + content.window, + "focusedWindow in child process in visible tab" + ); + Assert.ok( + content.document.hasFocus(), + "hasFocus in child process in visible tab" + ); + }); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tab2); +}); + +// Opens a document with a nested hierarchy of frames using initChildFrames and +// focuses each child iframe in turn. +add_task(async function test_subframes_focus() { + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + OOP_BASE_PAGE_URI + ); + + const markup = "<input>"; + + let browser = tab.linkedBrowser; + let browsingContexts = await initChildFrames(browser, markup); + + for (let blurSubframe of [true, false]) { + for (let index = browsingContexts.length - 1; index >= 0; index--) { + let bc = browsingContexts[index]; + + // Focus each browsing context in turn. Do this twice, once when the window + // is not already focused, and once when it is already focused. + for (let step = 0; step < 2; step++) { + let desc = + "within child frame " + + index + + " step " + + step + + " blur subframe " + + blurSubframe + + " "; + + info(desc + "start"); + await SimpleTest.promiseFocus(bc, false, blurSubframe); + + let expectedFocusedBC = bc; + // Becuase we are iterating backwards through the iframes, when we get to a frame + // that contains the iframe we just tested, focusing it will keep the child + // iframe focused as well, so we need to account for this when verifying which + // child iframe is focused. For the root frame (index 0), the iframe nested + // two items down will actually be focused. + // If blurSubframe is true however, the iframe focus in the parent will be cleared, + // so the focused window should be the parent instead. + if (!blurSubframe) { + if (index == 0) { + expectedFocusedBC = browsingContexts[index + 2]; + } else if (index == 3 || index == 1) { + expectedFocusedBC = browsingContexts[index + 1]; + } + } + is( + Services.focus.focusedContentBrowsingContext, + expectedFocusedBC, + desc + + " focusedContentBrowsingContext" + + ":: " + + Services.focus.focusedContentBrowsingContext?.id + + "," + + expectedFocusedBC?.id + ); + + // If the processes don't match, then the child iframe is an out-of-process iframe. + let oop = + expectedFocusedBC.currentWindowGlobal.osPid != + bc.currentWindowGlobal.osPid; + await SpecialPowers.spawn( + bc, + [ + index, + desc, + expectedFocusedBC != bc ? expectedFocusedBC : null, + oop, + ], + (num, descChild, childBC, isOop) => { + Assert.equal( + Services.focus.activeBrowsingContext, + content.browsingContext.top, + descChild + "activeBrowsingContext" + ); + Assert.ok( + content.document.hasFocus(), + descChild + "hasFocus: " + content.browsingContext.id + ); + + // If a child browsing context is expected to be focused, the focusedWindow + // should be set to that instead and the active element should be an iframe. + // Otherwise, the focused window should be this window, and the active + // element should be the document's body element. + if (childBC) { + // The frame structure is: + // A1 + // -> B + // -> A2 + // where A and B are two processes. The frame A2 starts out focused. When B is + // focused, A1's focus is updated correctly. + + // In Fission mode, childBC.window returns a non-null proxy even if OOP + if (isOop) { + Assert.equal( + Services.focus.focusedWindow, + null, + descChild + "focusedWindow" + ); + Assert.ok(!childBC.docShell, descChild + "childBC.docShell"); + } else { + Assert.equal( + Services.focus.focusedWindow, + childBC.window, + descChild + "focusedWindow" + ); + } + Assert.equal( + content.document.activeElement.localName, + "iframe", + descChild + "activeElement" + ); + } else { + Assert.equal( + Services.focus.focusedWindow, + content.window, + descChild + "focusedWindow" + ); + Assert.equal( + content.document.activeElement, + content.document.body, + descChild + "activeElement" + ); + } + } + ); + } + } + } + + // Focus the top window without blurring the browser. + await SimpleTest.promiseFocus(window, false, false); + is( + document.activeElement.localName, + "browser", + "focus after blurring browser blur subframe false" + ); + + // Now, focus the top window, blurring the browser. + await SimpleTest.promiseFocus(window, false, true); + is( + document.activeElement, + document.body, + "focus after blurring browser blur subframe true" + ); + + BrowserTestUtils.removeTab(tab); +}); diff --git a/browser/base/content/test/outOfProcess/file_base.html b/browser/base/content/test/outOfProcess/file_base.html new file mode 100644 index 0000000000..03f0731a8e --- /dev/null +++ b/browser/base/content/test/outOfProcess/file_base.html @@ -0,0 +1,5 @@ +<html><body> +<div id="insertPoint"></div> +<iframe src="https://www.mozilla.org:443/browser/browser/base/content/test/outOfProcess/file_frame1.html" width="320" height="700" style="border: 1px solid black;"></iframe></body> +<iframe src="https://test1.example.org:443/browser/browser/base/content/test/outOfProcess/file_frame2.html" width="320" height="700" style="border: 1px solid black;"></iframe></body> +</html> diff --git a/browser/base/content/test/outOfProcess/file_frame1.html b/browser/base/content/test/outOfProcess/file_frame1.html new file mode 100644 index 0000000000..d39e970c0f --- /dev/null +++ b/browser/base/content/test/outOfProcess/file_frame1.html @@ -0,0 +1,5 @@ +<html><body> +<div id="insertPoint"></div> +Same domain:<br> +<iframe src="file_innerframe.html" width="300" height="100" style="border: 1px solid black;"></iframe></body> +</html> diff --git a/browser/base/content/test/outOfProcess/file_frame2.html b/browser/base/content/test/outOfProcess/file_frame2.html new file mode 100644 index 0000000000..f0bc91ba20 --- /dev/null +++ b/browser/base/content/test/outOfProcess/file_frame2.html @@ -0,0 +1,11 @@ +<html><body> +<div id="insertPoint"></div> +Same domain as to the left:<br> +<iframe src="https://www.mozilla.org:443/browser/browser/base/content/test/outOfProcess/file_innerframe.html" width="300" height="100" style="border: 1px solid black;"></iframe></body> +Same domain as parent:<br> +<iframe src="https://test1.example.org:443/browser/browser/base/content/test/outOfProcess/file_innerframe.html" width="300" height="100" style="border: 1px solid black;"></iframe></body> +Different domain:<br> +<iframe src="https://w3c-test.org:443/browser/browser/base/content/test/outOfProcess/file_innerframe.html" width="300" height="100" style="border: 1px solid black;"></iframe></body> +Same as top-level domain:<br> +<iframe src="https://example.com/browser/browser/base/content/test/outOfProcess/file_innerframe.html" width="300" height="100" style="border: 1px solid black;"></iframe></body> +</html> diff --git a/browser/base/content/test/outOfProcess/file_innerframe.html b/browser/base/content/test/outOfProcess/file_innerframe.html new file mode 100644 index 0000000000..23c516232c --- /dev/null +++ b/browser/base/content/test/outOfProcess/file_innerframe.html @@ -0,0 +1,3 @@ +<html><body> +<div id="insertPoint"></div> +</html> diff --git a/browser/base/content/test/outOfProcess/head.js b/browser/base/content/test/outOfProcess/head.js new file mode 100644 index 0000000000..230e2e2cbc --- /dev/null +++ b/browser/base/content/test/outOfProcess/head.js @@ -0,0 +1,85 @@ +const OOP_BASE_PAGE_URI = + "https://example.com/browser/browser/base/content/test/outOfProcess/file_base.html"; + +// The number of frames and subframes that exist for the basic OOP test. If frames are +// modified within file_base.html, update this value. +const TOTAL_FRAME_COUNT = 8; + +// The frames are assigned different colors based on their process ids. If you add a +// frame you might need to add more colors to this list. +const FRAME_COLORS = ["white", "seashell", "lightcyan", "palegreen"]; + +/** + * Set up a set of child frames for the given browser for testing + * out of process frames. 'OOP_BASE_PAGE_URI' is the base page and subframes + * contain pages from the same or other domains. + * + * @param browser browser containing frame hierarchy to set up + * @param insertHTML HTML or function that returns what to insert into each frame + * @returns array of all browsing contexts in depth-first order + * + * This function adds a browsing context and process id label to each + * child subframe. It also sets the background color of each frame to + * different colors based on the process id. The browser_basic_outofprocess.js + * test verifies these colors to ensure that the frame/process hierarchy + * has been set up as expected. Colors are used to help people visualize + * the process setup. + * + * The insertHTML argument may be either a fixed string of HTML to insert + * into each subframe, or a function that returns the string to insert. The + * function takes one argument, the browsing context being processed. + */ +async function initChildFrames(browser, insertHTML) { + let colors = FRAME_COLORS.slice(); + let colorMap = new Map(); + + let browsingContexts = []; + + async function processBC(bc) { + browsingContexts.push(bc); + + let pid = bc.currentWindowGlobal.osPid; + let ident = "BrowsingContext: " + bc.id + "\nProcess: " + pid; + + let color = colorMap.get(pid); + if (!color) { + if (!colors.length) { + ok(false, "ran out of available colors"); + } + + color = colors.shift(); + colorMap.set(pid, color); + } + + let insertHTMLString = insertHTML; + if (typeof insertHTML == "function") { + insertHTMLString = insertHTML(bc); + } + + await SpecialPowers.spawn( + bc, + [ident, color, insertHTMLString], + (identChild, colorChild, insertHTMLChild) => { + let root = content.document.documentElement; + root.style = "background-color: " + colorChild; + + let pre = content.document.createElement("pre"); + pre.textContent = identChild; + root.insertBefore(pre, root.firstChild); + + if (insertHTMLChild) { + // eslint-disable-next-line no-unsanitized/property + content.document.getElementById("insertPoint").innerHTML = + insertHTMLChild; + } + } + ); + + for (let childBC of bc.children) { + await processBC(childBC); + } + } + await processBC(browser.browsingContext); + + return browsingContexts; +} |