/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Tests reporting of typing interactions. */ "use strict"; const TEST_URL = "https://example.com/browser/browser/components/places/tests/browser/keyword_form.html"; const TEST_URL2 = "https://example.com/browser"; const TEST_URL3 = "https://example.com/browser/browser/base/content/test/contextMenu/subtst_contextmenu_input.html"; const sentence = "The quick brown fox jumps over the lazy dog."; const sentenceFragments = [ "The quick", " brown fox", " jumps over the lazy dog.", ]; const longSentence = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas ut purus a libero cursus scelerisque. In hac habitasse platea dictumst. Quisque posuere ante sed consequat volutpat."; // For tests where it matters reduces the maximum time between keypresses to a length that we can // afford to delay the test by. const PREF_TYPING_DELAY = "browser.places.interactions.typing_timeout_ms"; const POST_TYPING_DELAY = 150; async function sendTextToInput(browser, text) { await SpecialPowers.spawn(browser, [], function () { const input = content.document.querySelector( "#form1 > input[name='search']" ); input.focus(); input.value = ""; // Reset to later verify that the provided text matches the value }); EventUtils.sendString(text); await SpecialPowers.spawn(browser, [{ text }], async function (args) { await ContentTaskUtils.waitForCondition( () => content.document.querySelector("#form1 > input[name='search']").value == args.text, "Text has been set on input" ); }); } add_task(async function test_load_and_navigate_away_no_keypresses() { await Interactions.reset(); await BrowserTestUtils.withNewTab(TEST_URL, async browser => { BrowserTestUtils.loadURIString(browser, TEST_URL2); await BrowserTestUtils.browserLoaded(browser, false, TEST_URL2); await assertDatabaseValues([ { url: TEST_URL, keypresses: 0, exactTypingTime: 0, }, ]); BrowserTestUtils.loadURIString(browser, "about:blank"); await BrowserTestUtils.browserLoaded(browser, false, "about:blank"); await assertDatabaseValues([ { url: TEST_URL, keypresses: 0, exactTypingTime: 0, }, { url: TEST_URL2, keypresses: 0, exactTypingTime: 0, }, ]); }); }); add_task(async function test_load_type_and_navigate_away() { await Interactions.reset(); await BrowserTestUtils.withNewTab(TEST_URL, async browser => { await sendTextToInput(browser, sentence); BrowserTestUtils.loadURIString(browser, TEST_URL2); await BrowserTestUtils.browserLoaded(browser, false, TEST_URL2); await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, typingTimeIsGreaterThan: 0, }, ]); BrowserTestUtils.loadURIString(browser, "about:blank"); await BrowserTestUtils.browserLoaded(browser, false, "about:blank"); await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, typingTimeIsGreaterThan: 0, }, { url: TEST_URL2, keypresses: 0, exactTypingTime: 0, }, ]); }); }); add_task(async function test_no_typing_close_tab() { await Interactions.reset(); await BrowserTestUtils.withNewTab(TEST_URL, async browser => {}); await assertDatabaseValues([ { url: TEST_URL, keypresses: 0, exactTypingTime: 0, }, ]); }); add_task(async function test_typing_close_tab() { await Interactions.reset(); await BrowserTestUtils.withNewTab(TEST_URL, async browser => { await sendTextToInput(browser, sentence); }); await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, typingTimeIsGreaterThan: 0, }, ]); }); add_task(async function test_single_key_typing_and_delay() { await Interactions.reset(); Services.prefs.setIntPref(PREF_TYPING_DELAY, POST_TYPING_DELAY); try { await BrowserTestUtils.withNewTab(TEST_URL, async browser => { // Single keystrokes with a delay between each, are not considered typing const text = ["T", "h", "e"]; for (let i = 0; i < text.length; i++) { await sendTextToInput(browser, text[i]); // We do need to wait here because typing is defined as a series of keystrokes followed by a delay. // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(r => setTimeout(r, POST_TYPING_DELAY * 2)); } }); } finally { Services.prefs.clearUserPref(PREF_TYPING_DELAY); } // Since we typed single keys with delays between each, there should be no typing added to the database await assertDatabaseValues([ { url: TEST_URL, keypresses: 0, exactTypingTime: 0, }, ]); }); add_task(async function test_double_key_typing_and_delay() { await Interactions.reset(); // Test three 2-key typing bursts. const text = ["Ab", "cd", "ef"]; const testStartTime = Cu.now(); Services.prefs.setIntPref(PREF_TYPING_DELAY, POST_TYPING_DELAY); try { await BrowserTestUtils.withNewTab(TEST_URL, async browser => { for (let i = 0; i < text.length; i++) { await sendTextToInput(browser, text[i]); // We do need to wait here because typing is defined as a series of keystrokes followed by a delay. // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(r => setTimeout(r, POST_TYPING_DELAY * 2)); } }); } finally { Services.prefs.clearUserPref(PREF_TYPING_DELAY); } // All keys should be recorded await assertDatabaseValues([ { url: TEST_URL, keypresses: text.reduce( (accumulator, current) => accumulator + current.length, 0 ), typingTimeIsLessThan: Cu.now() - testStartTime, }, ]); }); add_task(async function test_typing_and_delay() { await Interactions.reset(); const testStartTime = Cu.now(); Services.prefs.setIntPref(PREF_TYPING_DELAY, POST_TYPING_DELAY); try { await BrowserTestUtils.withNewTab(TEST_URL, async browser => { for (let i = 0; i < sentenceFragments.length; i++) { await sendTextToInput(browser, sentenceFragments[i]); // We do need to wait here because typing is defined as a series of keystrokes followed by a delay. // eslint-disable-next-line mozilla/no-arbitrary-setTimeout await new Promise(r => setTimeout(r, POST_TYPING_DELAY * 2)); } }); } finally { Services.prefs.clearUserPref(PREF_TYPING_DELAY); } await assertDatabaseValues([ { url: TEST_URL, keypresses: sentenceFragments.reduce( (accumulator, current) => accumulator + current.length, 0 ), typingTimeIsGreaterThan: 0, typingTimeIsLessThan: Cu.now() - testStartTime, }, ]); }); add_task(async function test_typing_and_reload() { await Interactions.reset(); const testStartTime = Cu.now(); await BrowserTestUtils.withNewTab(TEST_URL, async browser => { await sendTextToInput(browser, sentenceFragments[0]); info("reload"); browser.reload(); await BrowserTestUtils.browserLoaded(browser, false, TEST_URL); // First typing should have been recorded await assertDatabaseValues([ { url: TEST_URL, keypresses: sentenceFragments[0].length, typingTimeIsGreaterThan: 0, }, ]); await sendTextToInput(browser, sentenceFragments[1]); info("reload"); browser.reload(); await BrowserTestUtils.browserLoaded(browser, false, TEST_URL); // Second typing should have been recorded await assertDatabaseValues([ { url: TEST_URL, keypresses: sentenceFragments[0].length, typingTimeIsGreaterThan: 0, typingTimeIsLessThan: Cu.now() - testStartTime, }, { url: TEST_URL, keypresses: sentenceFragments[1].length, typingTimeIsGreaterThan: 0, typingTimeIsLessThan: Cu.now() - testStartTime, }, ]); }); }).skip(); // Bug 1749328 - intermittent failure: dropping the 2nd interaction after the 2nd reload add_task(async function test_switch_tabs_no_typing() { await Interactions.reset(); let tab1 = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: TEST_URL, }); let tab2 = BrowserTestUtils.addTab(gBrowser, TEST_URL2); await BrowserTestUtils.browserLoaded(tab2.linkedBrowser, false, TEST_URL2); info("Switch to second tab"); gBrowser.selectedTab = tab2; // Only the interaction of the first tab should be recorded so far, and with no typing await assertDatabaseValues([ { url: TEST_URL, keypresses: 0, exactTypingTime: 0, }, ]); BrowserTestUtils.removeTab(tab1); BrowserTestUtils.removeTab(tab2); }); add_task(async function test_typing_switch_tabs() { await Interactions.reset(); let tab1 = await BrowserTestUtils.openNewForegroundTab({ gBrowser, url: TEST_URL, }); await sendTextToInput(tab1.linkedBrowser, sentence); let tab2 = BrowserTestUtils.addTab(gBrowser, TEST_URL3); await BrowserTestUtils.browserLoaded(tab2.linkedBrowser, false, TEST_URL3); info("Switch to second tab"); await BrowserTestUtils.switchTab(gBrowser, tab2); // Only the interaction of the first tab should be recorded so far await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, typingTimeIsGreaterThan: 0, }, ]); const tab1TyingTime = await getDatabaseValue(TEST_URL, "typingTime"); info("Switch back to first tab"); await BrowserTestUtils.switchTab(gBrowser, tab1); // The interaction of the second tab should now be recorded (no typing) await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, exactTypingTime: tab1TyingTime, }, { url: TEST_URL3, keypresses: 0, exactTypingTime: 0, }, ]); info("Switch back to the second tab"); await BrowserTestUtils.switchTab(gBrowser, tab2); await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, exactTypingTime: tab1TyingTime, }, { url: TEST_URL3, keypresses: 0, exactTypingTime: 0, }, ]); // Typing into the second tab await SpecialPowers.spawn(tab2.linkedBrowser, [], function () { const input = content.document.getElementById("input_text"); input.focus(); }); await EventUtils.sendString(longSentence); await TestUtils.waitForTick(); info("Switch back to first tab"); await BrowserTestUtils.switchTab(gBrowser, tab1); // The interaction of the second tab should now also be recorded (with typing) await assertDatabaseValues([ { url: TEST_URL, keypresses: sentence.length, exactTypingTime: tab1TyingTime, }, { url: TEST_URL3, keypresses: longSentence.length, typingTimeIsGreaterThan: 0, }, ]); BrowserTestUtils.removeTab(tab1); BrowserTestUtils.removeTab(tab2); });