diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /toolkit/content/tests | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/content/tests')
297 files changed, 34631 insertions, 0 deletions
diff --git a/toolkit/content/tests/browser/.eslintrc.js b/toolkit/content/tests/browser/.eslintrc.js new file mode 100644 index 0000000000..c764b133dc --- /dev/null +++ b/toolkit/content/tests/browser/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/browser.eslintrc.js" + ] +}; diff --git a/toolkit/content/tests/browser/audio.ogg b/toolkit/content/tests/browser/audio.ogg Binary files differnew file mode 100644 index 0000000000..7f1833508a --- /dev/null +++ b/toolkit/content/tests/browser/audio.ogg diff --git a/toolkit/content/tests/browser/browser.ini b/toolkit/content/tests/browser/browser.ini new file mode 100644 index 0000000000..278b2ffe02 --- /dev/null +++ b/toolkit/content/tests/browser/browser.ini @@ -0,0 +1,75 @@ +[DEFAULT] +support-files = + head.js + file_contentTitle.html + audio.ogg + +[browser_audioCompeting.js] +tags = audiochannel +support-files = + file_multipleAudio.html +[browser_audioCompeting_onlyForActiveAgent.js] +tags = audiochannel +support-files = + file_multiplePlayingAudio.html +[browser_autoscroll_disabled.js] +[browser_block_autoplay_media.js] +tags = audiochannel +support-files = + file_multipleAudio.html +[browser_bug295977_autoscroll_overflow.js] +[browser_bug451286.js] +skip-if = !e10s +[browser_bug594509.js] +[browser_bug982298.js] +[browser_bug1198465.js] +[browser_contentTitle.js] +[browser_crash_previous_frameloader.js] +run-if = e10s && crashreporter +[browser_default_image_filename.js] +[browser_f7_caret_browsing.js] +[browser_findbar.js] +[browser_label_textlink.js] +[browser_isSynthetic.js] +support-files = + empty.png +[browser_keyevents_during_autoscrolling.js] +[browser_save_resend_postdata.js] +support-files = + common/mockTransfer.js + data/post_form_inner.sjs + data/post_form_outer.sjs +skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.contentDocument.getElementById("postForm").submit();) +[browser_content_url_annotation.js] +skip-if = !e10s || !crashreporter +support-files = + file_redirect.html + file_redirect_to.html +[browser_bug1170531.js] +[browser_mediaPlayback.js] +tags = audiochannel +support-files = + file_mediaPlayback.html + file_mediaPlaybackFrame.html +[browser_mediaPlayback_mute.js] +tags = audiochannel +support-files = + file_mediaPlayback2.html + file_mediaPlaybackFrame2.html +[browser_mediaPlayback_suspended.js] +tags = audiochannel +support-files = + file_mediaPlayback2.html +[browser_mediaPlayback_suspended_multipleAudio.js] +tags = audiochannel +support-files = + file_multipleAudio.html +[browser_mute.js] +tags = audiochannel +[browser_mute2.js] +tags = audiochannel +[browser_quickfind_editable.js] +[browser_saveImageURL.js] +support-files = + image.jpg + image_page.html diff --git a/toolkit/content/tests/browser/browser_audioCompeting.js b/toolkit/content/tests/browser/browser_audioCompeting.js new file mode 100644 index 0000000000..7b6a76c1da --- /dev/null +++ b/toolkit/content/tests/browser/browser_audioCompeting.js @@ -0,0 +1,115 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html"; + +function* wait_for_tab_playing_event(tab, expectPlaying) { + if (tab.soundPlaying == expectPlaying) { + ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + } else { + yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => { + if (event.detail.changed.indexOf("soundplaying") >= 0) { + is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } + return false; + }); + } +} + +function play_audio_from_invisible_tab () { + return new Promise(resolve => { + var autoPlay = content.document.getElementById('autoplay'); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.paused, true, "Audio in tab 1 was paused by audio competing."); + autoPlay.play(); + autoPlay.onpause = function() { + autoPlay.onpause = null; + ok(true, "Audio in tab 1 can't playback when other tab is playing in foreground."); + resolve(); + }; + }); +} + +function audio_should_keep_playing_even_go_to_background () { + var autoPlay = content.document.getElementById('autoplay'); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.paused, false, "Audio in tab 2 is still playing in the background."); +} + +function play_non_autoplay_audio () { + return new Promise(resolve => { + var autoPlay = content.document.getElementById('autoplay'); + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!autoPlay || !nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(nonAutoPlay.paused, true, "Non-autoplay audio isn't started playing yet."); + nonAutoPlay.play(); + + nonAutoPlay.onplay = function() { + nonAutoPlay.onplay = null; + is(nonAutoPlay.paused, false, "Start Non-autoplay audio."); + is(autoPlay.paused, false, "Autoplay audio is still playing."); + resolve(); + }; + }); +} + +add_task(function* setup_test_preference() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["dom.audiochannel.audioCompeting", true], + ["dom.ipc.processCount", 1] + ]}, resolve); + }); +}); + +add_task(function* cross_tabs_audio_competing () { + info("- open tab 1 in foreground -"); + let tab1 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, + "about:blank"); + tab1.linkedBrowser.loadURI(PAGE); + yield wait_for_tab_playing_event(tab1, true); + + info("- open tab 2 in foreground -"); + let tab2 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, + "about:blank"); + tab2.linkedBrowser.loadURI(PAGE); + yield wait_for_tab_playing_event(tab1, false); + + info("- open tab 3 in foreground -"); + let tab3 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, + "about:blank"); + yield ContentTask.spawn(tab2.linkedBrowser, null, + audio_should_keep_playing_even_go_to_background); + + info("- play audio from background tab 1 -"); + yield ContentTask.spawn(tab1.linkedBrowser, null, + play_audio_from_invisible_tab); + + info("- remove tabs -"); + yield BrowserTestUtils.removeTab(tab1); + yield BrowserTestUtils.removeTab(tab2); + yield BrowserTestUtils.removeTab(tab3); +}); + +add_task(function* within_one_tab_audio_competing () { + info("- open tab and play audio1 -"); + let tab = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, + "about:blank"); + tab.linkedBrowser.loadURI(PAGE); + yield wait_for_tab_playing_event(tab, true); + + info("- play audio2 in the same tab -"); + yield ContentTask.spawn(tab.linkedBrowser, null, + play_non_autoplay_audio); + + info("- remove tab -"); + yield BrowserTestUtils.removeTab(tab); +}); + diff --git a/toolkit/content/tests/browser/browser_audioCompeting_onlyForActiveAgent.js b/toolkit/content/tests/browser/browser_audioCompeting_onlyForActiveAgent.js new file mode 100644 index 0000000000..31cd3f6244 --- /dev/null +++ b/toolkit/content/tests/browser/browser_audioCompeting_onlyForActiveAgent.js @@ -0,0 +1,176 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multiplePlayingAudio.html"; + +var SuspendedType = { + NONE_SUSPENDED : 0, + SUSPENDED_PAUSE : 1, + SUSPENDED_BLOCK : 2, + SUSPENDED_PAUSE_DISPOSABLE : 3 +}; + +function wait_for_event(browser, event) { + return BrowserTestUtils.waitForEvent(browser, event, false, (event) => { + is(event.originalTarget, browser, "Event must be dispatched to correct browser."); + return true; + }); +} + +function check_all_audio_suspended(suspendedType) { + var audio1 = content.document.getElementById("audio1"); + var audio2 = content.document.getElementById("audio2"); + if (!audio1 || !audio2) { + ok(false, "Can't get the audio element!"); + } + + is(audio1.computedSuspended, suspendedType, + "The suspeded state of audio1 is correct."); + is(audio2.computedSuspended, suspendedType, + "The suspeded state of audio2 is correct."); +} + +function check_audio1_suspended(suspendedType) { + var audio1 = content.document.getElementById("audio1"); + if (!audio1) { + ok(false, "Can't get the audio element!"); + } + + is(audio1.computedSuspended, suspendedType, + "The suspeded state of audio1 is correct."); +} + +function check_audio2_suspended(suspendedType) { + var audio2 = content.document.getElementById("audio2"); + if (!audio2) { + ok(false, "Can't get the audio element!"); + } + + is(audio2.computedSuspended, suspendedType, + "The suspeded state of audio2 is correct."); +} + +function check_all_audio_pause_state(expectedPauseState) { + var audio1 = content.document.getElementById("audio1"); + var audio2 = content.document.getElementById("audio2"); + if (!audio1 | !audio2) { + ok(false, "Can't get the audio element!"); + } + + is(audio1.paused, expectedPauseState, + "The pause state of audio1 is correct."); + is(audio2.paused, expectedPauseState, + "The pause state of audio2 is correct."); +} + +function check_audio1_pause_state(expectedPauseState) { + var audio1 = content.document.getElementById("audio1"); + if (!audio1) { + ok(false, "Can't get the audio element!"); + } + + is(audio1.paused, expectedPauseState, + "The pause state of audio1 is correct."); +} + +function check_audio2_pause_state(expectedPauseState) { + var audio2 = content.document.getElementById("audio2"); + if (!audio2) { + ok(false, "Can't get the audio element!"); + } + + is(audio2.paused, expectedPauseState, + "The pause state of audio2 is correct."); +} + +function play_audio1_from_page() { + var audio1 = content.document.getElementById("audio1"); + if (!audio1) { + ok(false, "Can't get the audio element!"); + } + + is(audio1.paused, true, "Audio1 is paused."); + audio1.play(); + return new Promise(resolve => { + audio1.onplay = function() { + audio1.onplay = null; + ok(true, "Audio1 started playing."); + resolve(); + } + }); +} + +function stop_audio1_from_page() { + var audio1 = content.document.getElementById("audio1"); + if (!audio1) { + ok(false, "Can't get the audio element!"); + } + + is(audio1.paused, false, "Audio1 is playing."); + audio1.pause(); + return new Promise(resolve => { + audio1.onpause = function() { + audio1.onpause = null; + ok(true, "Audio1 stopped playing."); + resolve(); + } + }); +} + +function* audio_competing_for_active_agent(url, browser) { + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the default suspended state of all audio should be non-suspened -"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- only pause playing audio in the page -"); + browser.pauseMedia(true /* disposable */); + + info("- page shouldn't have any playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStopped"); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_all_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE, + check_all_audio_suspended); + + info("- resume audio1 from page -"); + yield ContentTask.spawn(browser, null, + play_audio1_from_page); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio1_suspended); + + info("- audio2 should still be suspended -"); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE, + check_audio2_suspended); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_audio2_pause_state); + + info("- stop audio1 from page -"); + yield ContentTask.spawn(browser, null, + stop_audio1_from_page); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio1_suspended); + + info("- audio2 should still be suspended -"); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE, + check_audio2_suspended); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_audio2_pause_state); + +} + +add_task(function* setup_test_preference() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["media.useAudioChannelService.testing", true], + ["dom.audiochannel.audioCompeting", true], + ["dom.audiochannel.audioCompeting.allAgents", true] + ]}); +}); + +add_task(function* test_suspended_pause_disposable() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, audio_competing_for_active_agent.bind(this, PAGE)); +}); diff --git a/toolkit/content/tests/browser/browser_autoscroll_disabled.js b/toolkit/content/tests/browser/browser_autoscroll_disabled.js new file mode 100644 index 0000000000..07c6174abd --- /dev/null +++ b/toolkit/content/tests/browser/browser_autoscroll_disabled.js @@ -0,0 +1,67 @@ +add_task(function* () +{ + const kPrefName_AutoScroll = "general.autoScroll"; + Services.prefs.setBoolPref(kPrefName_AutoScroll, false); + + let dataUri = 'data:text/html,<html><body id="i" style="overflow-y: scroll"><div style="height: 2000px"></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ +</body></html>'; + + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(dataUri); + yield loadedPromise; + + yield BrowserTestUtils.synthesizeMouse("#i", 50, 50, { button: 1 }, + gBrowser.selectedBrowser); + + yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { + var iframe = content.document.getElementById("iframe"); + + if (iframe) { + var e = new iframe.contentWindow.PageTransitionEvent("pagehide", + { bubbles: true, + cancelable: true, + persisted: false }); + iframe.contentDocument.dispatchEvent(e); + iframe.contentDocument.documentElement.dispatchEvent(e); + } + }); + + yield BrowserTestUtils.synthesizeMouse("#i", 100, 100, + { type: "mousemove", clickCount: "0" }, + gBrowser.selectedBrowser); + + // If scrolling didn't work, we wouldn't do any redraws and thus time out, so + // request and force redraws to get the chance to check for scrolling at all. + yield new Promise(resolve => window.requestAnimationFrame(resolve)); + + let msg = yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { + // Skip the first animation frame callback as it's the same callback that + // the browser uses to kick off the scrolling. + return new Promise(resolve => { + function checkScroll() { + let msg = ""; + let elem = content.document.getElementById('i'); + if (elem.scrollTop != 0) { + msg += "element should not have scrolled vertically"; + } + if (elem.scrollLeft != 0) { + msg += "element should not have scrolled horizontally"; + } + + resolve(msg); + } + + content.requestAnimationFrame(checkScroll); + }); + }); + + ok(!msg, "element scroll " + msg); + + // restore the changed prefs + if (Services.prefs.prefHasUserValue(kPrefName_AutoScroll)) + Services.prefs.clearUserPref(kPrefName_AutoScroll); + + // wait for focus to fix a failure in the next test if the latter runs too soon. + yield SimpleTest.promiseFocus(); +}); diff --git a/toolkit/content/tests/browser/browser_block_autoplay_media.js b/toolkit/content/tests/browser/browser_block_autoplay_media.js new file mode 100644 index 0000000000..3b2a309b9a --- /dev/null +++ b/toolkit/content/tests/browser/browser_block_autoplay_media.js @@ -0,0 +1,87 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html"; + +var SuspendedType = { + NONE_SUSPENDED : 0, + SUSPENDED_PAUSE : 1, + SUSPENDED_BLOCK : 2, + SUSPENDED_PAUSE_DISPOSABLE : 3 +}; + +function* wait_for_tab_playing_event(tab, expectPlaying) { + if (tab.soundPlaying == expectPlaying) { + ok(true, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + } else { + yield BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false, (event) => { + if (event.detail.changed.indexOf("soundplaying") >= 0) { + is(tab.soundPlaying, expectPlaying, "The tab should " + (expectPlaying ? "" : "not ") + "be playing"); + return true; + } + return false; + }); + } +} + +function check_audio_suspended(suspendedType) { + var autoPlay = content.document.getElementById('autoplay'); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.computedSuspended, suspendedType, + "The suspeded state of autoplay audio is correct."); +} + +add_task(function* setup_test_preference() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["media.useAudioChannelService.testing", true], + ["media.block-autoplay-until-in-foreground", true] + ]}, resolve); + }); +}); + +add_task(function* block_autoplay_media() { + info("- open new background tab1 -"); + let tab1 = window.gBrowser.addTab("about:blank"); + tab1.linkedBrowser.loadURI(PAGE); + yield BrowserTestUtils.browserLoaded(tab1.linkedBrowser); + + info("- should block autoplay media for non-visited tab1 -"); + yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.SUSPENDED_BLOCK, + check_audio_suspended); + + info("- open new background tab2 -"); + let tab2 = window.gBrowser.addTab("about:blank"); + tab2.linkedBrowser.loadURI(PAGE); + yield BrowserTestUtils.browserLoaded(tab2.linkedBrowser); + + info("- should block autoplay for non-visited tab2 -"); + yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.SUSPENDED_BLOCK, + check_audio_suspended); + + info("- select tab1 as foreground tab -"); + yield BrowserTestUtils.switchTab(window.gBrowser, tab1); + + info("- media should be unblocked because the tab was visited -"); + yield wait_for_tab_playing_event(tab1, true); + yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); + + info("- open another new foreground tab3 -"); + let tab3 = yield BrowserTestUtils.openNewForegroundTab(window.gBrowser, + "about:blank"); + info("- should still play media from tab1 -"); + yield wait_for_tab_playing_event(tab1, true); + yield ContentTask.spawn(tab1.linkedBrowser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); + + info("- should still block media from tab2 -"); + yield wait_for_tab_playing_event(tab2, false); + yield ContentTask.spawn(tab2.linkedBrowser, SuspendedType.SUSPENDED_BLOCK, + check_audio_suspended); + + info("- remove tabs -"); + yield BrowserTestUtils.removeTab(tab1); + yield BrowserTestUtils.removeTab(tab2); + yield BrowserTestUtils.removeTab(tab3); +}); diff --git a/toolkit/content/tests/browser/browser_bug1170531.js b/toolkit/content/tests/browser/browser_bug1170531.js new file mode 100644 index 0000000000..49df5661aa --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug1170531.js @@ -0,0 +1,92 @@ +// Test for bug 1170531 +// https://bugzilla.mozilla.org/show_bug.cgi?id=1170531 + +add_task(function* () { + // Get a bunch of DOM nodes + let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDOMWindowUtils); + + let editMenu = document.getElementById("edit-menu"); + let menubar = editMenu.parentNode; + let menuPopup = editMenu.menupopup; + let editMenuIndex = -1; + for (let i = 0; i < menubar.children.length; i++) { + if (menubar.children[i] === editMenu) { + editMenuIndex = i; + break; + } + } + + let closeMenu = function(aCallback) { + if (OS.Constants.Sys.Name == "Darwin") { + executeSoon(aCallback); + return; + } + + menuPopup.addEventListener("popuphidden", function onPopupHidden() { + menuPopup.removeEventListener("popuphidden", onPopupHidden, false); + executeSoon(aCallback); + }, false); + + executeSoon(function() { + editMenu.open = false; + }); + }; + + let openMenu = function(aCallback) { + if (OS.Constants.Sys.Name == "Darwin") { + goUpdateGlobalEditMenuItems(); + // On OSX, we have a native menu, so it has to be updated. In single process browsers, + // this happens synchronously, but in e10s, we have to wait for the main thread + // to deal with it for us. 1 second should be plenty of time. + setTimeout(aCallback, 1000); + return; + } + + menuPopup.addEventListener("popupshown", function onPopupShown() { + menuPopup.removeEventListener("popupshown", onPopupShown, false); + executeSoon(aCallback); + }, false); + + executeSoon(function() { + editMenu.open = true; + }); + }; + + yield BrowserTestUtils.withNewTab({ gBrowser: gBrowser, url: "about:blank" }, function* (browser) { + let menu_cut_disabled, menu_copy_disabled; + + yield BrowserTestUtils.loadURI(browser, "data:text/html,<div>hello!</div>"); + yield BrowserTestUtils.browserLoaded(browser); + browser.focus(); + yield new Promise(resolve => waitForFocus(resolve, window)); + yield new Promise(openMenu); + menu_cut_disabled = menuPopup.querySelector("#menu_cut").getAttribute('disabled') == "true"; + is(menu_cut_disabled, false, "menu_cut should be enabled"); + menu_copy_disabled = menuPopup.querySelector("#menu_copy").getAttribute('disabled') == "true"; + is(menu_copy_disabled, false, "menu_copy should be enabled"); + yield new Promise(closeMenu); + + yield BrowserTestUtils.loadURI(browser, "data:text/html,<div contentEditable='true'>hello!</div>"); + yield BrowserTestUtils.browserLoaded(browser); + browser.focus(); + yield new Promise(resolve => waitForFocus(resolve, window)); + yield new Promise(openMenu); + menu_cut_disabled = menuPopup.querySelector("#menu_cut").getAttribute('disabled') == "true"; + is(menu_cut_disabled, false, "menu_cut should be enabled"); + menu_copy_disabled = menuPopup.querySelector("#menu_copy").getAttribute('disabled') == "true"; + is(menu_copy_disabled, false, "menu_copy should be enabled"); + yield new Promise(closeMenu); + + yield BrowserTestUtils.loadURI(browser, "about:preferences"); + yield BrowserTestUtils.browserLoaded(browser); + browser.focus(); + yield new Promise(resolve => waitForFocus(resolve, window)); + yield new Promise(openMenu); + menu_cut_disabled = menuPopup.querySelector("#menu_cut").getAttribute('disabled') == "true"; + is(menu_cut_disabled, true, "menu_cut should be disabled"); + menu_copy_disabled = menuPopup.querySelector("#menu_copy").getAttribute('disabled') == "true"; + is(menu_copy_disabled, true, "menu_copy should be disabled"); + yield new Promise(closeMenu); + }); +}); diff --git a/toolkit/content/tests/browser/browser_bug1198465.js b/toolkit/content/tests/browser/browser_bug1198465.js new file mode 100644 index 0000000000..a9cc83e121 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug1198465.js @@ -0,0 +1,75 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var kPrefName = "accessibility.typeaheadfind.prefillwithselection"; +var kEmptyURI = "data:text/html,"; + +// This pref is false by default in OSX; ensure the test still works there. +Services.prefs.setBoolPref(kPrefName, true); + +registerCleanupFunction(function() { + Services.prefs.clearUserPref(kPrefName); +}); + +add_task(function* () { + let aTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kEmptyURI); + ok(!gFindBarInitialized, "findbar isn't initialized yet"); + + // Note: the use case here is when the user types directly in the findbar + // _before_ it's prefilled with a text selection in the page. + + // So `yield BrowserTestUtils.sendChar()` can't be used here: + // - synthesizing a key in the browser won't actually send it to the + // findbar; the findbar isn't part of the browser content. + // - we need to _not_ wait for _startFindDeferred to be resolved; yielding + // a synthesized keypress on the browser implicitely happens after the + // browser has dispatched its return message with the prefill value for + // the findbar, which essentially nulls these tests. + + let findBar = gFindBar; + is(findBar._findField.value, "", "findbar is empty"); + + // Test 1 + // Any input in the findbar should erase a previous search. + + findBar._findField.value = "xy"; + findBar.startFind(); + is(findBar._findField.value, "xy", "findbar should have xy initial query"); + is(findBar._findField.mInputField, + document.activeElement, + "findbar is now focused"); + + EventUtils.sendChar("z", window); + is(findBar._findField.value, "z", "z erases xy"); + + findBar._findField.value = ""; + ok(!findBar._findField.value, "erase findbar after first test"); + + // Test 2 + // Prefilling the findbar should be ignored if a search has been run. + + findBar.startFind(); + ok(findBar._startFindDeferred, "prefilled value hasn't been fetched yet"); + is(findBar._findField.mInputField, + document.activeElement, + "findbar is still focused"); + + EventUtils.sendChar("a", window); + EventUtils.sendChar("b", window); + is(findBar._findField.value, "ab", "initial ab typed in the findbar"); + + // This resolves _startFindDeferred if it's still pending; let's just skip + // over waiting for the browser's return message that should do this as it + // doesn't really matter. + findBar.onCurrentSelection("foo", true); + ok(!findBar._startFindDeferred, "prefilled value fetched"); + is(findBar._findField.value, "ab", "ab kept instead of prefill value"); + + EventUtils.sendChar("c", window); + is(findBar._findField.value, "abc", "c is appended after ab"); + + // Clear the findField value to make the test run successfully + // for multiple runs in the same browser session. + findBar._findField.value = ""; + yield BrowserTestUtils.removeTab(aTab); +}); diff --git a/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js new file mode 100644 index 0000000000..958afc868e --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js @@ -0,0 +1,214 @@ +requestLongerTimeout(2); +add_task(function* () +{ + function pushPref(name, value) { + return new Promise(resolve => SpecialPowers.pushPrefEnv({"set": [[name, value]]}, resolve)); + } + + yield pushPref("general.autoScroll", true); + + const expectScrollNone = 0; + const expectScrollVert = 1; + const expectScrollHori = 2; + const expectScrollBoth = 3; + + var allTests = [ + {dataUri: 'data:text/html,<html><head><meta charset="utf-8"></head><body><style type="text/css">div { display: inline-block; }</style>\ + <div id="a" style="width: 100px; height: 100px; overflow: hidden;"><div style="width: 200px; height: 200px;"></div></div>\ + <div id="b" style="width: 100px; height: 100px; overflow: auto;"><div style="width: 200px; height: 200px;"></div></div>\ + <div id="c" style="width: 100px; height: 100px; overflow-x: auto; overflow-y: hidden;"><div style="width: 200px; height: 200px;"></div></div>\ + <div id="d" style="width: 100px; height: 100px; overflow-y: auto; overflow-x: hidden;"><div style="width: 200px; height: 200px;"></div></div>\ + <select id="e" style="width: 100px; height: 100px;" multiple="multiple"><option>aaaaaaaaaaaaaaaaaaaaaaaa</option><option>a</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option></select>\ + <select id="f" style="width: 100px; height: 100px;"><option>a</option><option>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option>\ + <option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option><option>a</option></select>\ + <div id="g" style="width: 99px; height: 99px; border: 10px solid black; margin: 10px; overflow: auto;"><div style="width: 100px; height: 100px;"></div></div>\ + <div id="h" style="width: 100px; height: 100px; overflow: -moz-hidden-unscrollable;"><div style="width: 200px; height: 200px;"></div></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ + </body></html>'}, + {elem: 'a', expected: expectScrollNone}, + {elem: 'b', expected: expectScrollBoth}, + {elem: 'c', expected: expectScrollHori}, + {elem: 'd', expected: expectScrollVert}, + {elem: 'e', expected: expectScrollVert}, + {elem: 'f', expected: expectScrollNone}, + {elem: 'g', expected: expectScrollBoth}, + {elem: 'h', expected: expectScrollNone}, + {dataUri: 'data:text/html,<html><head><meta charset="utf-8"></head><body id="i" style="overflow-y: scroll"><div style="height: 2000px"></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ + </body></html>'}, + {elem: 'i', expected: expectScrollVert}, // bug 695121 + {dataUri: 'data:text/html,<html><head><meta charset="utf-8"></head><style>html, body { width: 100%; height: 100%; overflow-x: hidden; overflow-y: scroll; }</style>\ + <body id="j"><div style="height: 2000px"></div>\ + <iframe id="iframe" style="display: none;"></iframe>\ + </body></html>'}, + {elem: 'j', expected: expectScrollVert}, // bug 914251 + {dataUri: 'data:text/html,<html><head><meta charset="utf-8">\ +<style>\ +body > div {scroll-behavior: smooth;width: 300px;height: 300px;overflow: scroll;}\ +body > div > div {width: 1000px;height: 1000px;}\ +</style>\ +</head><body><div id="t"><div></div></div></body></html>'}, + {elem: 't', expected: expectScrollBoth}, // bug 1308775 + {dataUri: 'data:text/html,<html><head><meta charset="utf-8"></head><body>\ +<div id="k" style="height: 150px; width: 200px; overflow: scroll; border: 1px solid black;">\ +<iframe style="height: 200px; width: 300px;"></iframe>\ +</div>\ +<div id="l" style="height: 150px; width: 300px; overflow: scroll; border: 1px dashed black;">\ +<iframe style="height: 200px; width: 200px;" src="data:text/html,<div style=\'border: 5px solid blue; height: 200%; width: 200%;\'></div>"></iframe>\ +</div>\ +<iframe id="m"></iframe>\ +<div style="height: 200%; border: 5px dashed black;">filler to make document overflow: scroll;</div>\ +</body></html>'}, + {elem: 'k', expected: expectScrollBoth}, + {elem: 'k', expected: expectScrollNone, testwindow: true}, + {elem: 'l', expected: expectScrollNone}, + {elem: 'm', expected: expectScrollVert, testwindow: true}, + {dataUri: 'data:text/html,<html><head><meta charset="utf-8"></head><body>\ +<img width="100" height="100" alt="image map" usemap="%23planetmap">\ +<map name="planetmap">\ + <area id="n" shape="rect" coords="0,0,100,100" href="javascript:void(null)">\ +</map>\ +<a href="javascript:void(null)" id="o" style="width: 100px; height: 100px; border: 1px solid black; display: inline-block; vertical-align: top;">link</a>\ +<input id="p" style="width: 100px; height: 100px; vertical-align: top;">\ +<textarea id="q" style="width: 100px; height: 100px; vertical-align: top;"></textarea>\ +<div style="height: 200%; border: 1px solid black;"></div>\ +</body></html>'}, + {elem: 'n', expected: expectScrollNone, testwindow: true}, + {elem: 'o', expected: expectScrollNone, testwindow: true}, + {elem: 'p', expected: expectScrollVert, testwindow: true, middlemousepastepref: false}, + {elem: 'q', expected: expectScrollVert, testwindow: true, middlemousepastepref: false}, + {dataUri: 'data:text/html,<html><head><meta charset="utf-8"></head><body>\ +<input id="r" style="width: 100px; height: 100px; vertical-align: top;">\ +<textarea id="s" style="width: 100px; height: 100px; vertical-align: top;"></textarea>\ +<div style="height: 200%; border: 1px solid black;"></div>\ +</body></html>'}, + {elem: 'r', expected: expectScrollNone, testwindow: true, middlemousepastepref: true}, + {elem: 's', expected: expectScrollNone, testwindow: true, middlemousepastepref: true} + ]; + + for (let test of allTests) { + if (test.dataUri) { + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(test.dataUri); + yield loadedPromise; + continue; + } + + let prefsChanged = (test.middlemousepastepref == false || test.middlemousepastepref == true); + if (prefsChanged) { + yield pushPref("middlemouse.paste", test.middlemousepastepref); + } + + yield BrowserTestUtils.synthesizeMouse("#" + test.elem, 50, 80, { button: 1 }, + gBrowser.selectedBrowser); + + // This ensures bug 605127 is fixed: pagehide in an unrelated document + // should not cancel the autoscroll. + yield ContentTask.spawn(gBrowser.selectedBrowser, { }, function* () { + var iframe = content.document.getElementById("iframe"); + + if (iframe) { + var e = new iframe.contentWindow.PageTransitionEvent("pagehide", + { bubbles: true, + cancelable: true, + persisted: false }); + iframe.contentDocument.dispatchEvent(e); + iframe.contentDocument.documentElement.dispatchEvent(e); + } + }); + + is(document.activeElement, gBrowser.selectedBrowser, "Browser still focused after autoscroll started"); + + yield BrowserTestUtils.synthesizeMouse("#" + test.elem, 100, 100, + { type: "mousemove", clickCount: "0" }, + gBrowser.selectedBrowser); + + if (prefsChanged) { + yield new Promise(resolve => SpecialPowers.popPrefEnv(resolve)); + } + + // Start checking for the scroll. + let firstTimestamp = undefined; + let timeCompensation; + do { + let timestamp = yield new Promise(resolve => window.requestAnimationFrame(resolve)); + if (firstTimestamp === undefined) { + firstTimestamp = timestamp; + } + + // This value is calculated similarly to the value of the same name in + // ClickEventHandler.autoscrollLoop, except here it's cumulative across + // all frames after the first one instead of being based only on the + // current frame. + timeCompensation = (timestamp - firstTimestamp) / 20; + info("timestamp=" + timestamp + " firstTimestamp=" + firstTimestamp + + " timeCompensation=" + timeCompensation); + + // Try to wait until enough time has passed to allow the scroll to happen. + // autoscrollLoop incrementally scrolls during each animation frame, but + // due to how its calculations work, when a frame is very close to the + // previous frame, no scrolling may actually occur during that frame. + // After 100ms's worth of frames, timeCompensation will be 1, making it + // more likely that the accumulated scroll in autoscrollLoop will be >= 1, + // although it also depends on acceleration, which here in this test + // should be > 1 due to how it synthesizes mouse events below. + } while (timeCompensation < 5); + + // Close the autoscroll popup by synthesizing Esc. + EventUtils.synthesizeKey("VK_ESCAPE", {}); + let scrollVert = test.expected & expectScrollVert; + let scrollHori = test.expected & expectScrollHori; + + yield ContentTask.spawn(gBrowser.selectedBrowser, + { scrollVert : scrollVert, + scrollHori: scrollHori, + elemid : test.elem, + checkWindow: test.testwindow }, + function* (args) { + let msg = ""; + if (args.checkWindow) { + if (!((args.scrollVert && content.scrollY > 0) || + (!args.scrollVert && content.scrollY == 0))) { + msg += "Failed: "; + } + msg += 'Window for ' + args.elemid + ' should' + (args.scrollVert ? '' : ' not') + ' have scrolled vertically\n'; + + if (!((args.scrollHori && content.scrollX > 0) || + (!args.scrollHori && content.scrollX == 0))) { + msg += "Failed: "; + } + msg += ' Window for ' + args.elemid + ' should' + (args.scrollHori ? '' : ' not') + ' have scrolled horizontally\n'; + } else { + let elem = content.document.getElementById(args.elemid); + if (!((args.scrollVert && elem.scrollTop > 0) || + (!args.scrollVert && elem.scrollTop == 0))) { + msg += "Failed: "; + } + msg += ' ' + args.elemid + ' should' + (args.scrollVert ? '' : ' not') + ' have scrolled vertically\n'; + if (!((args.scrollHori && elem.scrollLeft > 0) || + (!args.scrollHori && elem.scrollLeft == 0))) { + msg += "Failed: "; + } + msg += args.elemid + ' should' + (args.scrollHori ? '' : ' not') + ' have scrolled horizontally'; + } + + Assert.ok(msg.indexOf("Failed") == -1, msg); + } + ); + + // Before continuing the test, we need to ensure that the IPC + // message that stops autoscrolling has had time to arrive. + yield new Promise(resolve => executeSoon(resolve)); + } + + // remove 2 tabs that were opened by middle-click on links + while (gBrowser.visibleTabs.length > 1) { + gBrowser.removeTab(gBrowser.visibleTabs[gBrowser.visibleTabs.length - 1]); + } + + // wait for focus to fix a failure in the next test if the latter runs too soon. + yield SimpleTest.promiseFocus(); +}); diff --git a/toolkit/content/tests/browser/browser_bug451286.js b/toolkit/content/tests/browser/browser_bug451286.js new file mode 100644 index 0000000000..a5dadeb849 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug451286.js @@ -0,0 +1,152 @@ +Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js", this); + +add_task(function*() { + const SEARCH_TEXT = "text"; + const DATAURI = "data:text/html," + SEARCH_TEXT; + + // Bug 451286. An iframe that should be highlighted + let visible = "<iframe id='visible' src='" + DATAURI + "'></iframe>"; + + // Bug 493658. An invisible iframe that shouldn't interfere with + // highlighting matches lying after it in the document + let invisible = "<iframe id='invisible' style='display: none;' " + + "src='" + DATAURI + "'></iframe>"; + + let uri = DATAURI + invisible + SEARCH_TEXT + visible + SEARCH_TEXT; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, uri); + let contentRect = tab.linkedBrowser.getBoundingClientRect(); + let noHighlightSnapshot = snapshotRect(window, contentRect); + ok(noHighlightSnapshot, "Got noHighlightSnapshot"); + + yield openFindBarAndWait(); + gFindBar._findField.value = SEARCH_TEXT; + yield findAgainAndWait(); + var matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) + matchCase.doCommand(); + + // Turn on highlighting + yield toggleHighlightAndWait(true); + yield closeFindBarAndWait(); + + // Take snapshot of highlighting + let findSnapshot = snapshotRect(window, contentRect); + ok(findSnapshot, "Got findSnapshot"); + + // Now, remove the highlighting, and take a snapshot to compare + // to our original state + yield openFindBarAndWait(); + yield toggleHighlightAndWait(false); + yield closeFindBarAndWait(); + + let unhighlightSnapshot = snapshotRect(window, contentRect); + ok(unhighlightSnapshot, "Got unhighlightSnapshot"); + + // Select the matches that should have been highlighted manually + yield ContentTask.spawn(tab.linkedBrowser, null, function*() { + let doc = content.document; + let win = doc.defaultView; + + // Create a manual highlight in the visible iframe to test bug 451286 + let iframe = doc.getElementById("visible"); + let ifBody = iframe.contentDocument.body; + let range = iframe.contentDocument.createRange(); + range.selectNodeContents(ifBody.childNodes[0]); + let ifWindow = iframe.contentWindow; + let ifDocShell = ifWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + let ifController = ifDocShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + + let frameFindSelection = + ifController.getSelection(ifController.SELECTION_FIND); + frameFindSelection.addRange(range); + + // Create manual highlights in the main document (the matches that lie + // before/after the iframes + let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + + let docFindSelection = + controller.getSelection(ifController.SELECTION_FIND); + + range = doc.createRange(); + range.selectNodeContents(doc.body.childNodes[0]); + docFindSelection.addRange(range); + range = doc.createRange(); + range.selectNodeContents(doc.body.childNodes[2]); + docFindSelection.addRange(range); + range = doc.createRange(); + range.selectNodeContents(doc.body.childNodes[4]); + docFindSelection.addRange(range); + }); + + // Take snapshot of manual highlighting + let manualSnapshot = snapshotRect(window, contentRect); + ok(manualSnapshot, "Got manualSnapshot"); + + // Test 1: Were the matches in iframe correctly highlighted? + let res = compareSnapshots(findSnapshot, manualSnapshot, true); + ok(res[0], "Matches found in iframe correctly highlighted"); + + // Test 2: Were the matches in iframe correctly unhighlighted? + res = compareSnapshots(noHighlightSnapshot, unhighlightSnapshot, true); + ok(res[0], "Highlighting in iframe correctly removed"); + + yield BrowserTestUtils.removeTab(tab); +}); + +function toggleHighlightAndWait(shouldHighlight) { + return new Promise((resolve) => { + let listener = { + onFindResult() {}, + onHighlightFinished() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + }, + onMatchesCountResult() {} + }; + gFindBar.browser.finder.addResultListener(listener); + gFindBar.toggleHighlight(shouldHighlight); + }); +} + +function findAgainAndWait() { + return new Promise(resolve => { + let listener = { + onFindResult() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + }, + onHighlightFinished() {}, + onMatchesCountResult() {} + }; + gFindBar.browser.finder.addResultListener(listener); + gFindBar.onFindAgainCommand(); + }); +} + +function* openFindBarAndWait() { + let awaitTransitionEnd = BrowserTestUtils.waitForEvent(gFindBar, "transitionend"); + gFindBar.open(); + yield awaitTransitionEnd; +} + +// This test is comparing snapshots. It is necessary to wait for the gFindBar +// to close before taking the snapshot so the gFindBar does not take up space +// on the new snapshot. +function* closeFindBarAndWait() { + let awaitTransitionEnd = BrowserTestUtils.waitForEvent(gFindBar, "transitionend", false, event => { + return event.propertyName == "visibility"; + }); + gFindBar.close(); + yield awaitTransitionEnd; +} diff --git a/toolkit/content/tests/browser/browser_bug594509.js b/toolkit/content/tests/browser/browser_bug594509.js new file mode 100644 index 0000000000..e67b05f852 --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug594509.js @@ -0,0 +1,9 @@ +add_task(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:rights"); + + yield ContentTask.spawn(tab.linkedBrowser, null, function* () { + Assert.ok(content.document.getElementById("your-rights"), "about:rights content loaded"); + }); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_bug982298.js b/toolkit/content/tests/browser/browser_bug982298.js new file mode 100644 index 0000000000..047340c5ce --- /dev/null +++ b/toolkit/content/tests/browser/browser_bug982298.js @@ -0,0 +1,70 @@ +const scrollHtml = + "<textarea id=\"textarea1\" row=2>Firefox\n\nFirefox\n\n\n\n\n\n\n\n\n\n" + + "</textarea><a href=\"about:blank\">blank</a>"; + +add_task(function*() { + let url = "data:text/html;base64," + btoa(scrollHtml); + yield BrowserTestUtils.withNewTab({gBrowser, url}, function*(browser) { + let awaitFindResult = new Promise(resolve => { + let listener = { + onFindResult(aData) { + info("got find result"); + browser.finder.removeResultListener(listener); + + ok(aData.result == Ci.nsITypeAheadFind.FIND_FOUND, "should find string"); + resolve(); + }, + onCurrentSelection() {}, + onMatchesCountResult() {} + }; + info("about to add results listener, open find bar, and send 'F' string"); + browser.finder.addResultListener(listener); + }); + gFindBar.onFindCommand(); + EventUtils.sendString("F"); + info("added result listener and sent string 'F'"); + yield awaitFindResult; + + let awaitScrollDone = BrowserTestUtils.waitForMessage(browser.messageManager, "ScrollDone"); + // scroll textarea to bottom + const scrollTest = + "var textarea = content.document.getElementById(\"textarea1\");" + + "textarea.scrollTop = textarea.scrollHeight;" + + "sendAsyncMessage(\"ScrollDone\", { });" + browser.messageManager.loadFrameScript("data:text/javascript;base64," + + btoa(scrollTest), false); + yield awaitScrollDone; + info("got ScrollDone event"); + yield BrowserTestUtils.loadURI(browser, "about:blank"); + yield BrowserTestUtils.browserLoaded(browser); + + ok(browser.currentURI.spec == "about:blank", "got load event for about:blank"); + + let awaitFindResult2 = new Promise(resolve => { + let listener = { + onFindResult(aData) { + info("got find result #2"); + browser.finder.removeResultListener(listener); + resolve(); + }, + onCurrentSelection() {}, + onMatchesCountResult() {} + }; + + browser.finder.addResultListener(listener); + info("added result listener"); + }); + // find again needs delay for crash test + setTimeout(function() { + // ignore exception if occured + try { + info("about to send find again command"); + gFindBar.onFindAgainCommand(false); + info("sent find again command"); + } catch (e) { + info("got exception from onFindAgainCommand: " + e); + } + }, 0); + yield awaitFindResult2; + }); +}); diff --git a/toolkit/content/tests/browser/browser_contentTitle.js b/toolkit/content/tests/browser/browser_contentTitle.js new file mode 100644 index 0000000000..e7966e5655 --- /dev/null +++ b/toolkit/content/tests/browser/browser_contentTitle.js @@ -0,0 +1,16 @@ +var url = "https://example.com/browser/toolkit/content/tests/browser/file_contentTitle.html"; + +add_task(function*() { + let tab = gBrowser.selectedTab = gBrowser.addTab(url); + let browser = tab.linkedBrowser; + yield new Promise((resolve) => { + addEventListener("TestLocationChange", function listener() { + removeEventListener("TestLocationChange", listener); + resolve(); + }, true, true); + }); + + is(gBrowser.contentTitle, "Test Page", "Should have the right title."); + + gBrowser.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_content_url_annotation.js b/toolkit/content/tests/browser/browser_content_url_annotation.js new file mode 100644 index 0000000000..1a4cee4c6c --- /dev/null +++ b/toolkit/content/tests/browser/browser_content_url_annotation.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* global Services, requestLongerTimeout, TestUtils, BrowserTestUtils, + ok, info, dump, is, Ci, Cu, Components, ctypes, privateNoteIntentionalCrash, + gBrowser, add_task, addEventListener, removeEventListener, ContentTask */ + +"use strict"; + +// Running this test in ASAN is slow. +requestLongerTimeout(2); + +/** + * Removes a file from a directory. This is a no-op if the file does not + * exist. + * + * @param directory + * The nsIFile representing the directory to remove from. + * @param filename + * A string for the file to remove from the directory. + */ +function removeFile(directory, filename) { + let file = directory.clone(); + file.append(filename); + if (file.exists()) { + file.remove(false); + } +} + +/** + * Returns the directory where crash dumps are stored. + * + * @return nsIFile + */ +function getMinidumpDirectory() { + let dir = Services.dirsvc.get('ProfD', Ci.nsIFile); + dir.append("minidumps"); + return dir; +} + +/** + * Checks that the URL is correctly annotated on a content process crash. + */ +add_task(function* test_content_url_annotation() { + let url = "https://example.com/browser/toolkit/content/tests/browser/file_redirect.html"; + let redirect_url = "https://example.com/browser/toolkit/content/tests/browser/file_redirect_to.html"; + + yield BrowserTestUtils.withNewTab({ + gBrowser: gBrowser + }, function* (browser) { + ok(browser.isRemoteBrowser, "Should be a remote browser"); + + // file_redirect.html should send us to file_redirect_to.html + let promise = ContentTask.spawn(browser, {}, function* () { + dump('ContentTask starting...\n'); + yield new Promise((resolve) => { + addEventListener("RedirectDone", function listener() { + dump('Got RedirectDone\n'); + removeEventListener("RedirectDone", listener); + resolve(); + }, true, true); + }); + }); + browser.loadURI(url); + yield promise; + + // Crash the tab + let annotations = yield BrowserTestUtils.crashBrowser(browser); + + ok("URL" in annotations, "annotated a URL"); + is(annotations.URL, redirect_url, + "Should have annotated the URL after redirect"); + }); +}); diff --git a/toolkit/content/tests/browser/browser_crash_previous_frameloader.js b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js new file mode 100644 index 0000000000..bd50c6ffde --- /dev/null +++ b/toolkit/content/tests/browser/browser_crash_previous_frameloader.js @@ -0,0 +1,108 @@ +"use strict"; + +/** + * Cleans up the .dmp and .extra file from a crash. + * + * @param subject (nsISupports) + * The subject passed through the ipc:content-shutdown + * observer notification when a content process crash has + * occurred. + */ +function cleanUpMinidump(subject) { + Assert.ok(subject instanceof Ci.nsIPropertyBag2, + "Subject needs to be a nsIPropertyBag2 to clean up properly"); + let dumpID = subject.getPropertyAsAString("dumpID"); + + Assert.ok(dumpID, "There should be a dumpID"); + if (dumpID) { + let dir = Services.dirsvc.get("ProfD", Ci.nsIFile); + dir.append("minidumps"); + + let file = dir.clone(); + file.append(dumpID + ".dmp"); + file.remove(true); + + file = dir.clone(); + file.append(dumpID + ".extra"); + file.remove(true); + } +} + +/** + * This test ensures that if a remote frameloader crashes after + * the frameloader owner swaps it out for a new frameloader, + * that a oop-browser-crashed event is not sent to the new + * frameloader's browser element. + */ +add_task(function* test_crash_in_previous_frameloader() { + // On debug builds, crashing tabs results in much thinking, which + // slows down the test and results in intermittent test timeouts, + // so we'll pump up the expected timeout for this test. + requestLongerTimeout(2); + + if (!gMultiProcessBrowser) { + Assert.ok(false, "This test should only be run in multi-process mode."); + return; + } + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "http://example.com", + }, function*(browser) { + // First, sanity check... + Assert.ok(browser.isRemoteBrowser, + "This browser needs to be remote if this test is going to " + + "work properly."); + + // We will wait for the oop-browser-crashed event to have + // a chance to appear. That event is fired when TabParents + // are destroyed, and that occurs _before_ ContentParents + // are destroyed, so we'll wait on the ipc:content-shutdown + // observer notification, which is fired when a ContentParent + // goes away. After we see this notification, oop-browser-crashed + // events should have fired. + let contentProcessGone = TestUtils.topicObserved("ipc:content-shutdown"); + let sawTabCrashed = false; + let onTabCrashed = () => { + sawTabCrashed = true; + }; + + browser.addEventListener("oop-browser-crashed", onTabCrashed); + + // The name of the game is to cause a crash in a remote browser, + // and then immediately swap out the browser for a non-remote one. + yield ContentTask.spawn(browser, null, function() { + const Cu = Components.utils; + Cu.import("resource://gre/modules/ctypes.jsm"); + Cu.import("resource://gre/modules/Timer.jsm"); + + let dies = function() { + privateNoteIntentionalCrash(); + let zero = new ctypes.intptr_t(8); + let badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t)); + badptr.contents + }; + + // When the parent flips the remoteness of the browser, the + // page should receive the pagehide event, which we'll then + // use to crash the frameloader. + addEventListener("pagehide", function() { + dump("\nEt tu, Brute?\n"); + dies(); + }); + }); + + gBrowser.updateBrowserRemoteness(browser, false); + info("Waiting for content process to go away."); + let [subject, data] = yield contentProcessGone; + + // If we don't clean up the minidump, the harness will + // complain. + cleanUpMinidump(subject); + + info("Content process is gone!"); + Assert.ok(!sawTabCrashed, + "Should not have seen the oop-browser-crashed event."); + browser.removeEventListener("oop-browser-crashed", onTabCrashed); + }); +}); diff --git a/toolkit/content/tests/browser/browser_default_image_filename.js b/toolkit/content/tests/browser/browser_default_image_filename.js new file mode 100644 index 0000000000..2859d486fb --- /dev/null +++ b/toolkit/content/tests/browser/browser_default_image_filename.js @@ -0,0 +1,45 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/** + * TestCase for bug 564387 + * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387> + */ +add_task(function* () { + let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI("data:image/gif;base64,R0lGODlhEAAOALMAAOazToeHh0tLS/7LZv/0jvb29t/f3//Ub//ge8WSLf/rhf/3kdbW1mxsbP//mf///yH5BAAAAAAALAAAAAAQAA4AAARe8L1Ekyky67QZ1hLnjM5UUde0ECwLJoExKcppV0aCcGCmTIHEIUEqjgaORCMxIC6e0CcguWw6aFjsVMkkIr7g77ZKPJjPZqIyd7sJAgVGoEGv2xsBxqNgYPj/gAwXEQA7"); + yield loadPromise; + + let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown"); + + yield BrowserTestUtils.synthesizeMouseAtCenter("img", + { type: "contextmenu", button: 2 }, + gBrowser.selectedBrowser); + + yield popupShownPromise; + + let showFilePickerPromise = new Promise(resolve => { + MockFilePicker.showCallback = function(fp) { + is(fp.defaultString, "index.gif"); + resolve(); + } + }); + + registerCleanupFunction(function () { + MockFilePicker.cleanup(); + }); + + // Select "Save Image As" option from context menu + var saveImageAsCommand = document.getElementById("context-saveimage"); + saveImageAsCommand.doCommand(); + + yield showFilePickerPromise; + + let contextMenu = document.getElementById("contentAreaContextMenu"); + let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden"); + contextMenu.hidePopup(); + yield popupHiddenPromise; +}); diff --git a/toolkit/content/tests/browser/browser_f7_caret_browsing.js b/toolkit/content/tests/browser/browser_f7_caret_browsing.js new file mode 100644 index 0000000000..c4b6823d41 --- /dev/null +++ b/toolkit/content/tests/browser/browser_f7_caret_browsing.js @@ -0,0 +1,227 @@ +var gListener = null; +const kURL = "data:text/html;charset=utf-8,Caret browsing is fun.<input id='in'>"; + +const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled"; +const kPrefWarnOnEnable = "accessibility.warn_on_browsewithcaret"; +const kPrefCaretBrowsingOn = "accessibility.browsewithcaret"; + +var oldPrefs = {}; +for (let pref of [kPrefShortcutEnabled, kPrefWarnOnEnable, kPrefCaretBrowsingOn]) { + oldPrefs[pref] = Services.prefs.getBoolPref(pref); +} + +Services.prefs.setBoolPref(kPrefShortcutEnabled, true); +Services.prefs.setBoolPref(kPrefWarnOnEnable, true); +Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + +registerCleanupFunction(function() { + for (let pref of [kPrefShortcutEnabled, kPrefWarnOnEnable, kPrefCaretBrowsingOn]) { + Services.prefs.setBoolPref(pref, oldPrefs[pref]); + } +}); + +// NB: not using BrowserTestUtils.domWindowOpened here because there's no way to +// undo waiting for a window open. If we don't want the window to be opened, and +// wait for it to verify that it indeed does not open, we need to be able to +// then "stop" waiting so that when we next *do* want it to open, our "old" +// listener doesn't fire and do things we don't want (like close the window...). +let gCaretPromptOpeningObserver; +function promiseCaretPromptOpened() { + return new Promise(resolve => { + function observer(subject, topic, data) { + if (topic == "domwindowopened") { + Services.ww.unregisterNotification(observer); + let win = subject.QueryInterface(Ci.nsIDOMWindow); + BrowserTestUtils.waitForEvent(win, "load", false, e => e.target.location.href != "about:blank").then(() => resolve(win)); + gCaretPromptOpeningObserver = null; + } + } + Services.ww.registerNotification(observer); + gCaretPromptOpeningObserver = observer; + }); +} + +function hitF7(async = true) { + let f7 = () => EventUtils.sendKey("F7"); + // Need to not stop execution inside this task: + if (async) { + executeSoon(f7); + } else { + f7(); + } +} + +function syncToggleCaretNoDialog(expected) { + let openedDialog = false; + promiseCaretPromptOpened().then(function(win) { + openedDialog = true; + win.close(); // This will eventually return focus here and allow the test to continue... + }); + // Cause the dialog to appear sync, if it still does. + hitF7(false); + + let expectedStr = expected ? "on." : "off."; + ok(!openedDialog, "Shouldn't open a dialog to turn caret browsing " + expectedStr); + // Need to clean up if the dialog wasn't opened, so the observer doesn't get + // re-triggered later on causing "issues". + if (!openedDialog) { + Services.ww.unregisterNotification(gCaretPromptOpeningObserver); + gCaretPromptOpeningObserver = null; + } + let prefVal = Services.prefs.getBoolPref(kPrefCaretBrowsingOn); + is(prefVal, expected, "Caret browsing should now be " + expectedStr); +} + +function waitForFocusOnInput(browser) +{ + return ContentTask.spawn(browser, null, function* () { + let textEl = content.document.getElementById("in"); + return ContentTaskUtils.waitForCondition(() => { + return content.document.activeElement == textEl; + }, "Input should get focused."); + }); +} + +function focusInput(browser) +{ + return ContentTask.spawn(browser, null, function* () { + let textEl = content.document.getElementById("in"); + textEl.focus(); + }); +} + +add_task(function* checkTogglingCaretBrowsing() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kURL); + yield focusInput(tab.linkedBrowser); + + let promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + let prompt = yield promiseGotKey; + let doc = prompt.document; + is(doc.documentElement.defaultButton, "cancel", "No button should be the default"); + ok(!doc.getElementById("checkbox").checked, "Checkbox shouldn't be checked by default."); + let promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + doc.documentElement.cancelDialog(); + yield promiseDialogUnloaded; + info("Dialog unloaded"); + yield waitForFocusOnInput(tab.linkedBrowser); + ok(!Services.prefs.getBoolPref(kPrefCaretBrowsingOn), "Caret browsing should still be off after cancelling the dialog."); + + promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + prompt = yield promiseGotKey; + + doc = prompt.document; + is(doc.documentElement.defaultButton, "cancel", "No button should be the default"); + ok(!doc.getElementById("checkbox").checked, "Checkbox shouldn't be checked by default."); + promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + doc.documentElement.acceptDialog(); + yield promiseDialogUnloaded; + info("Dialog unloaded"); + yield waitForFocusOnInput(tab.linkedBrowser); + ok(Services.prefs.getBoolPref(kPrefCaretBrowsingOn), "Caret browsing should be on after accepting the dialog."); + + syncToggleCaretNoDialog(false); + + promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + prompt = yield promiseGotKey; + doc = prompt.document; + + is(doc.documentElement.defaultButton, "cancel", "No button should be the default"); + ok(!doc.getElementById("checkbox").checked, "Checkbox shouldn't be checked by default."); + + promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + doc.documentElement.cancelDialog(); + yield promiseDialogUnloaded; + info("Dialog unloaded"); + yield waitForFocusOnInput(tab.linkedBrowser); + + ok(!Services.prefs.getBoolPref(kPrefCaretBrowsingOn), "Caret browsing should still be off after cancelling the dialog."); + + Services.prefs.setBoolPref(kPrefShortcutEnabled, true); + Services.prefs.setBoolPref(kPrefWarnOnEnable, true); + Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + + yield BrowserTestUtils.removeTab(tab); +}); + +add_task(function* toggleCheckboxNoCaretBrowsing() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kURL); + yield focusInput(tab.linkedBrowser); + + let promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + let prompt = yield promiseGotKey; + let doc = prompt.document; + is(doc.documentElement.defaultButton, "cancel", "No button should be the default"); + let checkbox = doc.getElementById("checkbox"); + ok(!checkbox.checked, "Checkbox shouldn't be checked by default."); + + // Check the box: + checkbox.click(); + + let promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + // Say no: + doc.documentElement.getButton("cancel").click(); + + yield promiseDialogUnloaded; + info("Dialog unloaded"); + yield waitForFocusOnInput(tab.linkedBrowser); + ok(!Services.prefs.getBoolPref(kPrefCaretBrowsingOn), "Caret browsing should still be off."); + ok(!Services.prefs.getBoolPref(kPrefShortcutEnabled), "Shortcut should now be disabled."); + + syncToggleCaretNoDialog(false); + ok(!Services.prefs.getBoolPref(kPrefShortcutEnabled), "Shortcut should still be disabled."); + + Services.prefs.setBoolPref(kPrefShortcutEnabled, true); + Services.prefs.setBoolPref(kPrefWarnOnEnable, true); + Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + + yield BrowserTestUtils.removeTab(tab); +}); + + +add_task(function* toggleCheckboxWantCaretBrowsing() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, kURL); + yield focusInput(tab.linkedBrowser); + + let promiseGotKey = promiseCaretPromptOpened(); + hitF7(); + let prompt = yield promiseGotKey; + let doc = prompt.document; + is(doc.documentElement.defaultButton, "cancel", "No button should be the default"); + let checkbox = doc.getElementById("checkbox"); + ok(!checkbox.checked, "Checkbox shouldn't be checked by default."); + + // Check the box: + checkbox.click(); + + let promiseDialogUnloaded = BrowserTestUtils.waitForEvent(prompt, "unload"); + + // Say yes: + doc.documentElement.acceptDialog(); + yield promiseDialogUnloaded; + info("Dialog unloaded"); + yield waitForFocusOnInput(tab.linkedBrowser); + ok(Services.prefs.getBoolPref(kPrefCaretBrowsingOn), "Caret browsing should now be on."); + ok(Services.prefs.getBoolPref(kPrefShortcutEnabled), "Shortcut should still be enabled."); + ok(!Services.prefs.getBoolPref(kPrefWarnOnEnable), "Should no longer warn when enabling."); + + syncToggleCaretNoDialog(false); + syncToggleCaretNoDialog(true); + syncToggleCaretNoDialog(false); + + Services.prefs.setBoolPref(kPrefShortcutEnabled, true); + Services.prefs.setBoolPref(kPrefWarnOnEnable, true); + Services.prefs.setBoolPref(kPrefCaretBrowsingOn, false); + + yield BrowserTestUtils.removeTab(tab); +}); + + + + diff --git a/toolkit/content/tests/browser/browser_findbar.js b/toolkit/content/tests/browser/browser_findbar.js new file mode 100644 index 0000000000..1ab06f6327 --- /dev/null +++ b/toolkit/content/tests/browser/browser_findbar.js @@ -0,0 +1,249 @@ +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +Components.utils.import("resource://gre/modules/Timer.jsm", this); + +const TEST_PAGE_URI = "data:text/html;charset=utf-8,The letter s."; +// Using 'javascript' schema to bypass E10SUtils.canLoadURIInProcess, because +// it does not allow 'data:' URI to be loaded in the parent process. +const E10S_PARENT_TEST_PAGE_URI = "javascript:document.write('The letter s.');"; + +/** + * Makes sure that the findbar hotkeys (' and /) event listeners + * are added to the system event group and do not get blocked + * by calling stopPropagation on a keypress event on a page. + */ +add_task(function* test_hotkey_event_propagation() { + info("Ensure hotkeys are not affected by stopPropagation."); + + // Opening new tab + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = gBrowser.getFindBar(); + + // Pressing these keys open the findbar. + const HOTKEYS = ["/", "'"]; + + // Checking if findbar appears when any hotkey is pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + yield SimpleTest.promiseFocus(gBrowser.selectedBrowser); + yield BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, false, "Findbar should not be hidden."); + yield closeFindbarAndWait(findbar); + } + + // Stop propagation for all keyboard events. + let frameScript = () => { + const stopPropagation = e => e.stopImmediatePropagation(); + let window = content.document.defaultView; + window.removeEventListener("keydown", stopPropagation); + window.removeEventListener("keypress", stopPropagation); + window.removeEventListener("keyup", stopPropagation); + }; + + let mm = browser.messageManager; + mm.loadFrameScript("data:,(" + frameScript.toString() + ")();", false); + + // Checking if findbar still appears when any hotkey is pressed. + for (let key of HOTKEYS) { + is(findbar.hidden, true, "Findbar is hidden now."); + gBrowser.selectedTab = tab; + yield SimpleTest.promiseFocus(gBrowser.selectedBrowser); + yield BrowserTestUtils.sendChar(key, browser); + is(findbar.hidden, false, "Findbar should not be hidden."); + yield closeFindbarAndWait(findbar); + } + + gBrowser.removeTab(tab); +}); + +add_task(function* test_not_found() { + info("Check correct 'Phrase not found' on new tab"); + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + + // Search for the first word. + yield promiseFindFinished("--- THIS SHOULD NEVER MATCH ---", false); + let findbar = gBrowser.getFindBar(); + is(findbar._findStatusDesc.textContent, findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'"); + + gBrowser.removeTab(tab); +}); + +add_task(function* test_found() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + + // Search for a string that WILL be found, with 'Highlight All' on + yield promiseFindFinished("S", true); + ok(!gBrowser.getFindBar()._findStatusDesc.textContent, + "Findbar status should be empty"); + + gBrowser.removeTab(tab); +}); + +// Setting first findbar to case-sensitive mode should not affect +// new tab find bar. +add_task(function* test_tabwise_case_sensitive() { + let tab1 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + let findbar1 = gBrowser.getFindBar(); + + let tab2 = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + let findbar2 = gBrowser.getFindBar(); + + // Toggle case sensitivity for first findbar + findbar1.getElement("find-case-sensitive").click(); + + gBrowser.selectedTab = tab1; + + // Not found for first tab. + yield promiseFindFinished("S", true); + is(findbar1._findStatusDesc.textContent, findbar1._notFoundStr, + "Findbar status text should be 'Phrase not found'"); + + gBrowser.selectedTab = tab2; + + // But it didn't affect the second findbar. + yield promiseFindFinished("S", true); + ok(!findbar2._findStatusDesc.textContent, "Findbar status should be empty"); + + gBrowser.removeTab(tab1); + gBrowser.removeTab(tab2); +}); + +/** + * Navigating from a web page (for example mozilla.org) to an internal page + * (like about:addons) might trigger a change of browser's remoteness. + * 'Remoteness change' means that rendering page content moves from child + * process into the parent process or the other way around. + * This test ensures that findbar properly handles such a change. + */ +add_task(function* test_reinitialization_at_remoteness_change() { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return; + } + + info("Ensure findbar re-initialization at remoteness change."); + + // Load a remote page and trigger findbar construction. + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + let browser = gBrowser.getBrowserForTab(tab); + let findbar = gBrowser.getFindBar(); + + // Findbar should operate normally. + yield promiseFindFinished("z", false); + is(findbar._findStatusDesc.textContent, findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'"); + + yield promiseFindFinished("s", false); + ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty"); + + // Moving browser into the parent process and reloading sample data. + ok(browser.isRemoteBrowser, "Browser should be remote now."); + yield promiseRemotenessChange(tab, false); + yield BrowserTestUtils.loadURI(browser, E10S_PARENT_TEST_PAGE_URI); + ok(!browser.isRemoteBrowser, "Browser should not be remote any more."); + + // Findbar should keep operating normally after remoteness change. + yield promiseFindFinished("z", false); + is(findbar._findStatusDesc.textContent, findbar._notFoundStr, + "Findbar status text should be 'Phrase not found'"); + + yield promiseFindFinished("s", false); + ok(!findbar._findStatusDesc.textContent, "Findbar status should be empty"); + + yield BrowserTestUtils.removeTab(tab); +}); + +/** + * Ensure that the initial typed characters aren't lost immediately after + * opening the find bar. + */ +add_task(function* () { + // This test only makes sence in e10s evironment. + if (!gMultiProcessBrowser) { + info("Skipping this test because of non-e10s environment."); + return; + } + + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PAGE_URI); + let browser = tab.linkedBrowser; + + ok(!gFindBarInitialized, "findbar isn't initialized yet"); + + let findBar = gFindBar; + let initialValue = findBar._findField.value; + + EventUtils.synthesizeKey("f", { accelKey: true }, window); + + let promises = [ + BrowserTestUtils.sendChar("a", browser), + BrowserTestUtils.sendChar("b", browser), + BrowserTestUtils.sendChar("c", browser) + ]; + + isnot(document.activeElement, findBar._findField.inputField, + "findbar is not yet focused"); + is(findBar._findField.value, initialValue, "still has initial find query"); + + yield Promise.all(promises); + is(document.activeElement, findBar._findField.inputField, + "findbar is now focused"); + is(findBar._findField.value, "abc", "abc fully entered as find query"); + + yield BrowserTestUtils.removeTab(tab); +}); + +function promiseFindFinished(searchText, highlightOn) { + let deferred = Promise.defer(); + + let findbar = gBrowser.getFindBar(); + findbar.startFind(findbar.FIND_NORMAL); + let highlightElement = findbar.getElement("highlight"); + if (highlightElement.checked != highlightOn) + highlightElement.click(); + executeSoon(() => { + findbar._findField.value = searchText; + + let resultListener; + // When highlighting is on the finder sends a second "FOUND" message after + // the search wraps. This causes timing problems with e10s. waitMore + // forces foundOrTimeout wait for the second "FOUND" message before + // resolving the promise. + let waitMore = highlightOn; + let findTimeout = setTimeout(() => foundOrTimedout(null), 2000); + let foundOrTimedout = function(aData) { + if (aData !== null && waitMore) { + waitMore = false; + return; + } + if (aData === null) + info("Result listener not called, timeout reached."); + clearTimeout(findTimeout); + findbar.browser.finder.removeResultListener(resultListener); + deferred.resolve(); + } + + resultListener = { + onFindResult: foundOrTimedout + }; + findbar.browser.finder.addResultListener(resultListener); + findbar._find(); + }); + + return deferred.promise; +} + +function promiseRemotenessChange(tab, shouldBeRemote) { + return new Promise((resolve) => { + let browser = gBrowser.getBrowserForTab(tab); + tab.addEventListener("TabRemotenessChange", function listener() { + tab.removeEventListener("TabRemotenessChange", listener); + resolve(); + }); + gBrowser.updateBrowserRemoteness(browser, shouldBeRemote); + }); +} diff --git a/toolkit/content/tests/browser/browser_isSynthetic.js b/toolkit/content/tests/browser/browser_isSynthetic.js new file mode 100644 index 0000000000..15a341461a --- /dev/null +++ b/toolkit/content/tests/browser/browser_isSynthetic.js @@ -0,0 +1,72 @@ +function LocationChangeListener(browser) { + this.browser = browser; + browser.addProgressListener(this); +} + +LocationChangeListener.prototype = { + wasSynthetic: false, + browser: null, + + destroy: function() { + this.browser.removeProgressListener(this); + }, + + onLocationChange: function(webProgress, request, location, flags) { + this.wasSynthetic = this.browser.isSyntheticDocument; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) +} + +const FILES = gTestPath.replace("browser_isSynthetic.js", "") + .replace("chrome://mochitests/content/", "http://example.com/"); + +function waitForPageShow(browser) { + return ContentTask.spawn(browser, null, function*() { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + yield new Promise(resolve => { + let listener = () => { + removeEventListener("pageshow", listener, true); + resolve(); + } + addEventListener("pageshow", listener, true); + }); + }); +} + +add_task(function*() { + let tab = gBrowser.addTab("about:blank"); + let browser = tab.linkedBrowser; + yield BrowserTestUtils.browserLoaded(browser); + let listener = new LocationChangeListener(browser); + + is(browser.isSyntheticDocument, false, "Should not be synthetic"); + + let loadPromise = waitForPageShow(browser); + browser.loadURI("data:text/html;charset=utf-8,<html/>"); + yield loadPromise; + is(listener.wasSynthetic, false, "Should not be synthetic"); + is(browser.isSyntheticDocument, false, "Should not be synthetic"); + + loadPromise = waitForPageShow(browser); + browser.loadURI(FILES + "empty.png"); + yield loadPromise; + is(listener.wasSynthetic, true, "Should be synthetic"); + is(browser.isSyntheticDocument, true, "Should be synthetic"); + + loadPromise = waitForPageShow(browser); + browser.goBack(); + yield loadPromise; + is(listener.wasSynthetic, false, "Should not be synthetic"); + is(browser.isSyntheticDocument, false, "Should not be synthetic"); + + loadPromise = waitForPageShow(browser); + browser.goForward(); + yield loadPromise; + is(listener.wasSynthetic, true, "Should be synthetic"); + is(browser.isSyntheticDocument, true, "Should be synthetic"); + + listener.destroy(); + gBrowser.removeTab(tab); +}); diff --git a/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js new file mode 100644 index 0000000000..3fce471141 --- /dev/null +++ b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js @@ -0,0 +1,120 @@ +add_task(function * () +{ + const kPrefName_AutoScroll = "general.autoScroll"; + Services.prefs.setBoolPref(kPrefName_AutoScroll, true); + + const kNoKeyEvents = 0; + const kKeyDownEvent = 1; + const kKeyPressEvent = 2; + const kKeyUpEvent = 4; + const kAllKeyEvents = 7; + + var expectedKeyEvents; + var dispatchedKeyEvents; + var key; + var root; + + /** + * Encapsulates EventUtils.sendChar(). + */ + function sendChar(aChar) + { + key = aChar; + dispatchedKeyEvents = kNoKeyEvents; + EventUtils.sendChar(key); + is(dispatchedKeyEvents, expectedKeyEvents, + "unexpected key events were dispatched or not dispatched: " + key); + } + + /** + * Encapsulates EventUtils.sendKey(). + */ + function sendKey(aKey) + { + key = aKey; + dispatchedKeyEvents = kNoKeyEvents; + EventUtils.sendKey(key); + is(dispatchedKeyEvents, expectedKeyEvents, + "unexpected key events were dispatched or not dispatched: " + key); + } + + function onKey(aEvent) + { +// if (aEvent.target != root && aEvent.target != root.ownerDocument.body) { +// ok(false, "unknown target: " + aEvent.target.tagName); +// return; +// } + + var keyFlag; + switch (aEvent.type) { + case "keydown": + keyFlag = kKeyDownEvent; + break; + case "keypress": + keyFlag = kKeyPressEvent; + break; + case "keyup": + keyFlag = kKeyUpEvent; + break; + default: + ok(false, "Unknown events: " + aEvent.type); + return; + } + dispatchedKeyEvents |= keyFlag; + is(keyFlag, expectedKeyEvents & keyFlag, aEvent.type + " fired: " + key); + } + + var dataUri = 'data:text/html,<body style="height:10000px;"></body>'; + + let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + gBrowser.loadURI(dataUri); + yield loadedPromise; + + yield SimpleTest.promiseFocus(gBrowser.selectedBrowser); + + window.addEventListener("keydown", onKey, false); + window.addEventListener("keypress", onKey, false); + window.addEventListener("keyup", onKey, false); + + // Test whether the key events are handled correctly under normal condition + expectedKeyEvents = kAllKeyEvents; + sendChar("A"); + + // Start autoscrolling by middle button click on the page + let shownPromise = BrowserTestUtils.waitForEvent(window, "popupshown", false, + event => event.originalTarget.className == "autoscroller"); + yield BrowserTestUtils.synthesizeMouseAtPoint(10, 10, { button: 1 }, + gBrowser.selectedBrowser); + yield shownPromise; + + // Most key events should be eaten by the browser. + expectedKeyEvents = kNoKeyEvents; + sendChar("A"); + sendKey("DOWN"); + sendKey("RETURN"); + sendKey("RETURN"); + sendKey("HOME"); + sendKey("END"); + sendKey("TAB"); + sendKey("RETURN"); + + // Finish autoscrolling by ESC key. Note that only keydown and keypress + // events are eaten because keyup event is fired *after* the autoscrolling + // is finished. + expectedKeyEvents = kKeyUpEvent; + sendKey("ESCAPE"); + + // Test whether the key events are handled correctly under normal condition + expectedKeyEvents = kAllKeyEvents; + sendChar("A"); + + window.removeEventListener("keydown", onKey, false); + window.removeEventListener("keypress", onKey, false); + window.removeEventListener("keyup", onKey, false); + + // restore the changed prefs + if (Services.prefs.prefHasUserValue(kPrefName_AutoScroll)) + Services.prefs.clearUserPref(kPrefName_AutoScroll); + + finish(); +}); diff --git a/toolkit/content/tests/browser/browser_label_textlink.js b/toolkit/content/tests/browser/browser_label_textlink.js new file mode 100644 index 0000000000..861086707e --- /dev/null +++ b/toolkit/content/tests/browser/browser_label_textlink.js @@ -0,0 +1,38 @@ +add_task(function* () { + yield BrowserTestUtils.withNewTab({gBrowser, url: "about:config"}, function*(browser) { + let newTabURL = "http://www.example.com/"; + yield ContentTask.spawn(browser, newTabURL, function*(newTabURL) { + let doc = content.document; + let label = doc.createElement("label"); + label.href = newTabURL; + label.id = "textlink-test"; + label.className = "text-link"; + label.textContent = "click me"; + doc.documentElement.append(label); + }); + + // Test that click will open tab in foreground. + let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + yield BrowserTestUtils.synthesizeMouseAtCenter("#textlink-test", {}, browser); + let newTab = yield awaitNewTab; + is(newTab.linkedBrowser, gBrowser.selectedBrowser, "selected tab should be example page"); + yield BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // Test that ctrl+shift+click/meta+shift+click will open tab in background. + awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + yield BrowserTestUtils.synthesizeMouseAtCenter("#textlink-test", + {ctrlKey: true, metaKey: true, shiftKey: true}, + browser); + yield awaitNewTab; + is(gBrowser.selectedBrowser, browser, "selected tab should be original tab"); + yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + + // Middle-clicking should open tab in foreground. + awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser, newTabURL); + yield BrowserTestUtils.synthesizeMouseAtCenter("#textlink-test", + {button: 1}, browser); + newTab = yield awaitNewTab; + is(newTab.linkedBrowser, gBrowser.selectedBrowser, "selected tab should be example page"); + yield BrowserTestUtils.removeTab(gBrowser.tabs[gBrowser.tabs.length - 1]); + }); +}); diff --git a/toolkit/content/tests/browser/browser_mediaPlayback.js b/toolkit/content/tests/browser/browser_mediaPlayback.js new file mode 100644 index 0000000000..1a6ebfcb83 --- /dev/null +++ b/toolkit/content/tests/browser/browser_mediaPlayback.js @@ -0,0 +1,30 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_mediaPlayback.html"; +const FRAME = "https://example.com/browser/toolkit/content/tests/browser/file_mediaPlaybackFrame.html"; + +function wait_for_event(browser, event) { + return BrowserTestUtils.waitForEvent(browser, event, false, (event) => { + is(event.originalTarget, browser, "Event must be dispatched to correct browser."); + ok(!event.cancelable, "The event should not be cancelable"); + return true; + }); +} + +function* test_on_browser(url, browser) { + browser.loadURI(url); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + yield wait_for_event(browser, "DOMAudioPlaybackStopped"); +} + +add_task(function* test_page() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, test_on_browser.bind(undefined, PAGE)); +}); + +add_task(function* test_frame() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, test_on_browser.bind(undefined, FRAME)); +}); diff --git a/toolkit/content/tests/browser/browser_mediaPlayback_mute.js b/toolkit/content/tests/browser/browser_mediaPlayback_mute.js new file mode 100644 index 0000000000..852fc56fb1 --- /dev/null +++ b/toolkit/content/tests/browser/browser_mediaPlayback_mute.js @@ -0,0 +1,104 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_mediaPlayback2.html"; +const FRAME = "https://example.com/browser/toolkit/content/tests/browser/file_mediaPlaybackFrame2.html"; + +function wait_for_event(browser, event) { + return BrowserTestUtils.waitForEvent(browser, event, false, (event) => { + is(event.originalTarget, browser, "Event must be dispatched to correct browser."); + return true; + }); +} + +function* test_audio_in_browser() { + function get_audio_element() { + var doc = content.document; + var list = doc.getElementsByTagName('audio'); + if (list.length == 1) { + return list[0]; + } + + // iframe? + list = doc.getElementsByTagName('iframe'); + + var iframe = list[0]; + list = iframe.contentDocument.getElementsByTagName('audio'); + return list[0]; + } + + var audio = get_audio_element(); + return { + computedVolume: audio.computedVolume, + computedMuted: audio.computedMuted + } +} + +function* test_on_browser(url, browser) { + browser.loadURI(url); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + var result = yield ContentTask.spawn(browser, null, test_audio_in_browser); + is(result.computedVolume, 1, "Audio volume is 1"); + is(result.computedMuted, false, "Audio is not muted"); + + ok(!browser.audioMuted, "Audio should not be muted by default"); + browser.mute(); + ok(browser.audioMuted, "Audio should be muted now"); + + yield wait_for_event(browser, "DOMAudioPlaybackStopped"); + + result = yield ContentTask.spawn(browser, null, test_audio_in_browser); + is(result.computedVolume, 0, "Audio volume is 0 when muted"); + is(result.computedMuted, true, "Audio is muted"); +} + +function* test_visibility(url, browser) { + browser.loadURI(url); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + var result = yield ContentTask.spawn(browser, null, test_audio_in_browser); + is(result.computedVolume, 1, "Audio volume is 1"); + is(result.computedMuted, false, "Audio is not muted"); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, function() {}); + + ok(!browser.audioMuted, "Audio should not be muted by default"); + browser.mute(); + ok(browser.audioMuted, "Audio should be muted now"); + + yield wait_for_event(browser, "DOMAudioPlaybackStopped"); + + result = yield ContentTask.spawn(browser, null, test_audio_in_browser); + is(result.computedVolume, 0, "Audio volume is 0 when muted"); + is(result.computedMuted, true, "Audio is muted"); +} + +add_task(function*() { + yield new Promise((resolve) => { + SpecialPowers.pushPrefEnv({"set": [ + ["media.useAudioChannelService.testing", true] + ]}, resolve); + }); +}); + +add_task(function* test_page() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, test_on_browser.bind(undefined, PAGE)); +}); + +add_task(function* test_frame() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, test_on_browser.bind(undefined, FRAME)); +}); + +add_task(function* test_frame() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank", + }, test_visibility.bind(undefined, PAGE)); +}); diff --git a/toolkit/content/tests/browser/browser_mediaPlayback_suspended.js b/toolkit/content/tests/browser/browser_mediaPlayback_suspended.js new file mode 100644 index 0000000000..ef8bb9dc82 --- /dev/null +++ b/toolkit/content/tests/browser/browser_mediaPlayback_suspended.js @@ -0,0 +1,191 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_mediaPlayback2.html"; + +var SuspendedType = { + NONE_SUSPENDED : 0, + SUSPENDED_PAUSE : 1, + SUSPENDED_BLOCK : 2, + SUSPENDED_PAUSE_DISPOSABLE : 3 +}; + +function wait_for_event(browser, event) { + return BrowserTestUtils.waitForEvent(browser, event, false, (event) => { + is(event.originalTarget, browser, "Event must be dispatched to correct browser."); + return true; + }); +} + +function check_audio_onplay() { + var list = content.document.getElementsByTagName('audio'); + if (list.length != 1) { + ok(false, "There should be only one audio element in page!") + } + + var audio = list[0]; + return new Promise((resolve, reject) => { + audio.onplay = () => { + ok(needToReceiveOnPlay, "Should not receive play event!"); + this.onplay = null; + reject(); + }; + + audio.pause(); + audio.play(); + + setTimeout(() => { + ok(true, "Doesn't receive play event when media was blocked."); + audio.onplay = null; + resolve(); + }, 1000) + }); +} + +function check_audio_suspended(suspendedType) { + var list = content.document.getElementsByTagName('audio'); + if (list.length != 1) { + ok(false, "There should be only one audio element in page!") + } + + var audio = list[0]; + is(audio.computedSuspended, suspendedType, + "The suspended state of MediaElement is correct."); +} + +function check_audio_pause_state(expectedPauseState) { + var list = content.document.getElementsByTagName('audio'); + if (list.length != 1) { + ok(false, "There should be only one audio element in page!") + } + + var audio = list[0]; + if (expectedPauseState) { + is(audio.paused, true, "Audio is paused correctly."); + } else { + is(audio.paused, false, "Audio is resumed correctly."); + } +} + +function* suspended_pause(url, browser) { + info("### Start test for suspended-pause ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the suspended state of audio should be non-suspened -"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); + + info("- pause playing audio -"); + browser.pauseMedia(false /* non-disposable */); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE, + check_audio_suspended); + + info("- resume paused audio -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, false /* expect for playing */, + check_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); +} + +function* suspended_pause_disposable(url, browser) { + info("### Start test for suspended-pause-disposable ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the suspended state of audio should be non-suspened -"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); + + info("- pause playing audio -"); + browser.pauseMedia(true /* disposable */); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE, + check_audio_suspended); + + info("- resume paused audio -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, false /* expect for playing */, + check_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); +} + +function* suspended_stop_disposable(url, browser) { + info("### Start test for suspended-stop-disposable ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the suspended state of audio should be non-suspened -"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); + + info("- stop playing audio -"); + browser.stopMedia(); + yield wait_for_event(browser, "DOMAudioPlaybackStopped"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); +} + +function* suspended_block(url, browser) { + info("### Start test for suspended-block ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- block playing audio -"); + browser.blockMedia(); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_BLOCK, + check_audio_suspended); + yield ContentTask.spawn(browser, null, + check_audio_onplay); + + info("- resume blocked audio -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_audio_suspended); +} + +add_task(function* setup_test_preference() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["media.useAudioChannelService.testing", true] + ]}, resolve); + }); +}); + +add_task(function* test_suspended_pause() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_pause.bind(this, PAGE)); +}); + +add_task(function* test_suspended_pause_disposable() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_pause_disposable.bind(this, PAGE)); +}); + +add_task(function* test_suspended_stop_disposable() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_stop_disposable.bind(this, PAGE)); +}); + +add_task(function* test_suspended_block() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_block.bind(this, PAGE)); +}); diff --git a/toolkit/content/tests/browser/browser_mediaPlayback_suspended_multipleAudio.js b/toolkit/content/tests/browser/browser_mediaPlayback_suspended_multipleAudio.js new file mode 100644 index 0000000000..12e2ec0774 --- /dev/null +++ b/toolkit/content/tests/browser/browser_mediaPlayback_suspended_multipleAudio.js @@ -0,0 +1,311 @@ +const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_multipleAudio.html"; + +var SuspendedType = { + NONE_SUSPENDED : 0, + SUSPENDED_PAUSE : 1, + SUSPENDED_BLOCK : 2, + SUSPENDED_PAUSE_DISPOSABLE : 3 +}; + +function wait_for_event(browser, event) { + return BrowserTestUtils.waitForEvent(browser, event, false, (event) => { + is(event.originalTarget, browser, "Event must be dispatched to correct browser."); + return true; + }); +} + +function check_all_audio_suspended(suspendedType) { + var autoPlay = content.document.getElementById('autoplay'); + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!autoPlay || !nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.computedSuspended, suspendedType, + "The suspeded state of autoplay audio is correct."); + is(nonAutoPlay.computedSuspended, suspendedType, + "The suspeded state of non-autoplay audio is correct."); +} + +function check_autoplay_audio_suspended(suspendedType) { + var autoPlay = content.document.getElementById('autoplay'); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.computedSuspended, suspendedType, + "The suspeded state of autoplay audio is correct."); +} + +function check_nonautoplay_audio_suspended(suspendedType) { + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(nonAutoPlay.computedSuspended, suspendedType, + "The suspeded state of non-autoplay audio is correct."); +} + +function check_autoplay_audio_pause_state(expectedPauseState) { + var autoPlay = content.document.getElementById('autoplay'); + if (!autoPlay) { + ok(false, "Can't get the audio element!"); + } + + if (autoPlay.paused == expectedPauseState) { + if (expectedPauseState) { + ok(true, "Audio is paused correctly."); + } else { + ok(true, "Audio is resumed correctly."); + } + } else if (expectedPauseState) { + autoPlay.onpause = function () { + autoPlay.onpause = null; + ok(true, "Audio is paused correctly, checking from onpause."); + } + } else { + autoPlay.onplay = function () { + autoPlay.onplay = null; + ok(true, "Audio is resumed correctly, checking from onplay."); + } + } +} + +function play_nonautoplay_audio_should_be_paused() { + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + nonAutoPlay.play(); + return new Promise(resolve => { + nonAutoPlay.onpause = function () { + nonAutoPlay.onpause = null; + is(nonAutoPlay.ended, false, "Audio can't be playback."); + resolve(); + } + }); +} + +function all_audio_onresume() { + var autoPlay = content.document.getElementById('autoplay'); + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!autoPlay || !nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.paused, false, "Autoplay audio is resumed."); + is(nonAutoPlay.paused, false, "Non-AutoPlay audio is resumed."); +} + +function all_audio_onpause() { + var autoPlay = content.document.getElementById('autoplay'); + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!autoPlay || !nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.paused, true, "Autoplay audio is paused."); + is(nonAutoPlay.paused, true, "Non-AutoPlay audio is paused."); +} + +function play_nonautoplay_audio_should_play_until_ended() { + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + nonAutoPlay.play(); + return new Promise(resolve => { + nonAutoPlay.onended = function () { + nonAutoPlay.onended = null; + ok(true, "Audio can be playback until ended."); + resolve(); + } + }); +} + +function no_audio_resumed() { + var autoPlay = content.document.getElementById('autoplay'); + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!autoPlay || !nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + is(autoPlay.paused && nonAutoPlay.paused, true, "No audio was resumed."); +} + +function play_nonautoplay_audio_should_be_blocked(suspendedType) { + var nonAutoPlay = content.document.getElementById('nonautoplay'); + if (!nonAutoPlay) { + ok(false, "Can't get the audio element!"); + } + + nonAutoPlay.play(); + ok(nonAutoPlay.paused, "The blocked audio can't be playback."); +} + +function* suspended_pause(url, browser) { + info("### Start test for suspended-pause ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the default suspended state of all audio should be non-suspened-"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- pause all audio in the page -"); + browser.pauseMedia(false /* non-disposable */); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_autoplay_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE, + check_autoplay_audio_suspended); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_nonautoplay_audio_suspended); + + info("- no audio can be playback during suspended-paused -"); + yield ContentTask.spawn(browser, null, + play_nonautoplay_audio_should_be_paused); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE, + check_nonautoplay_audio_suspended); + + info("- both audio should be resumed at the same time -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, null, + all_audio_onresume); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- both audio should be paused at the same time -"); + browser.pauseMedia(false /* non-disposable */); + yield ContentTask.spawn(browser, null, all_audio_onpause); +} + +function* suspended_pause_disposable(url, browser) { + info("### Start test for suspended-pause-disposable ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the default suspended state of all audio should be non-suspened -"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- only pause playing audio in the page -"); + browser.pauseMedia(true /* non-disposable */); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_autoplay_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_PAUSE_DISPOSABLE, + check_autoplay_audio_suspended); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_nonautoplay_audio_suspended); + + info("- new playing audio should be playback correctly -"); + yield ContentTask.spawn(browser, null, + play_nonautoplay_audio_should_play_until_ended); + + info("- should only resume one audio -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, false /* expect for playing */, + check_autoplay_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); +} + +function* suspended_stop_disposable(url, browser) { + info("### Start test for suspended-stop-disposable ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the default suspended state of all audio should be non-suspened -"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- only stop playing audio in the page -"); + browser.stopMedia(); + yield wait_for_event(browser, "DOMAudioPlaybackStopped"); + yield ContentTask.spawn(browser, true /* expect for pause */, + check_autoplay_audio_pause_state); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- new playing audio should be playback correctly -"); + yield ContentTask.spawn(browser, null, + play_nonautoplay_audio_should_play_until_ended); + + info("- no any audio can be resumed by page -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, null, no_audio_resumed); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); +} + +function* suspended_block(url, browser) { + info("### Start test for suspended-block ###"); + browser.loadURI(url); + + info("- page should have playing audio -"); + yield wait_for_event(browser, "DOMAudioPlaybackStarted"); + + info("- the default suspended state of all audio should be non-suspened-"); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); + + info("- block autoplay audio -"); + browser.blockMedia(); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_BLOCK, + check_autoplay_audio_suspended); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_nonautoplay_audio_suspended); + + info("- no audio can be playback during suspended-block -"); + yield ContentTask.spawn(browser, SuspendedType.SUSPENDED_BLOCK, + play_nonautoplay_audio_should_be_blocked); + + info("- both audio should be resumed at the same time -"); + browser.resumeMedia(); + yield ContentTask.spawn(browser, SuspendedType.NONE_SUSPENDED, + check_all_audio_suspended); +} + +add_task(function* setup_test_preference() { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({"set": [ + ["media.useAudioChannelService.testing", true] + ]}, resolve); + }); +}); + +add_task(function* test_suspended_pause() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_pause.bind(this, PAGE)); +}); + +add_task(function* test_suspended_pause_disposable() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_pause_disposable.bind(this, PAGE)); +}); + +add_task(function* test_suspended_stop_disposable() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_stop_disposable.bind(this, PAGE)); +}); + +add_task(function* test_suspended_block() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "about:blank" + }, suspended_block.bind(this, PAGE)); +}); diff --git a/toolkit/content/tests/browser/browser_mute.js b/toolkit/content/tests/browser/browser_mute.js new file mode 100644 index 0000000000..f4829b8082 --- /dev/null +++ b/toolkit/content/tests/browser/browser_mute.js @@ -0,0 +1,16 @@ +const PAGE = "data:text/html,page"; + +function* test_on_browser(browser) { + ok(!browser.audioMuted, "Audio should not be muted by default"); + browser.mute(); + ok(browser.audioMuted, "Audio should be muted now"); + browser.unmute(); + ok(!browser.audioMuted, "Audio should be unmuted now"); +} + +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, test_on_browser); +}); diff --git a/toolkit/content/tests/browser/browser_mute2.js b/toolkit/content/tests/browser/browser_mute2.js new file mode 100644 index 0000000000..38f415b71b --- /dev/null +++ b/toolkit/content/tests/browser/browser_mute2.js @@ -0,0 +1,26 @@ +const PAGE = "data:text/html,page"; + +function* test_on_browser(browser) { + ok(!browser.audioMuted, "Audio should not be muted by default"); + browser.mute(); + ok(browser.audioMuted, "Audio should be muted now"); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, test_on_browser2); + + browser.unmute(); + ok(!browser.audioMuted, "Audio should be unmuted now"); +} + +function* test_on_browser2(browser) { + ok(!browser.audioMuted, "Audio should not be muted by default"); +} + +add_task(function*() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE, + }, test_on_browser); +}); diff --git a/toolkit/content/tests/browser/browser_quickfind_editable.js b/toolkit/content/tests/browser/browser_quickfind_editable.js new file mode 100644 index 0000000000..d4ab597448 --- /dev/null +++ b/toolkit/content/tests/browser/browser_quickfind_editable.js @@ -0,0 +1,47 @@ +const PAGE = "data:text/html,<div contenteditable>foo</div><input><textarea></textarea>"; +const DESIGNMODE_PAGE = "data:text/html,<body onload='document.designMode=\"on\";'>"; +const HOTKEYS = ["/", "'"]; + +function* test_hotkeys(browser, expected) { + let findbar = gBrowser.getFindBar(); + for (let key of HOTKEYS) { + is(findbar.hidden, true, "findbar is hidden"); + yield BrowserTestUtils.sendChar(key, gBrowser.selectedBrowser); + is(findbar.hidden, expected, "findbar should" + (expected ? "" : " not") + " be hidden"); + if (!expected) { + yield closeFindbarAndWait(findbar); + } + } +} + +function* focus_element(browser, query) { + yield ContentTask.spawn(browser, query, function* focus(query) { + let element = content.document.querySelector(query); + element.focus(); + }); +} + +add_task(function* test_hotkey_on_editable_element() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: PAGE + }, function* do_tests(browser) { + yield test_hotkeys(browser, false); + const ELEMENTS = ["div", "input", "textarea"]; + for (let elem of ELEMENTS) { + yield focus_element(browser, elem); + yield test_hotkeys(browser, true); + yield focus_element(browser, ":root"); + yield test_hotkeys(browser, false); + } + }); +}); + +add_task(function* test_hotkey_on_designMode_document() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: DESIGNMODE_PAGE + }, function* do_tests(browser) { + yield test_hotkeys(browser, true); + }); +}); diff --git a/toolkit/content/tests/browser/browser_saveImageURL.js b/toolkit/content/tests/browser/browser_saveImageURL.js new file mode 100644 index 0000000000..75e1cfdcdb --- /dev/null +++ b/toolkit/content/tests/browser/browser_saveImageURL.js @@ -0,0 +1,68 @@ +"use strict"; + +const IMAGE_PAGE = "https://example.com/browser/toolkit/content/tests/browser/image_page.html"; +const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser"; + +MockFilePicker.init(window); +MockFilePicker.returnValue = MockFilePicker.returnCancel; + +registerCleanupFunction(function() { + MockFilePicker.cleanup(); +}); + +function waitForFilePicker() { + return new Promise((resolve) => { + MockFilePicker.showCallback = () => { + MockFilePicker.showCallback = null; + ok(true, "Saw the file picker"); + resolve(); + } + }) +} + +/** + * Test that saveImageURL works when we pass in the aIsContentWindowPrivate + * argument instead of a document. This is the preferred API. + */ +add_task(function* preferred_API() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: IMAGE_PAGE, + }, function*(browser) { + let url = yield ContentTask.spawn(browser, null, function*() { + let image = content.document.getElementById("image"); + return image.href; + }); + + saveImageURL(url, "image.jpg", null, true, false, null, null, null, null, false); + yield waitForFilePicker(); + }); +}); + +/** + * Test that saveImageURL will still work when passed a document instead + * of the aIsContentWindowPrivate argument. This is the deprecated API, and + * will not work in apps using remote browsers having PREF_UNSAFE_FORBIDDEN + * set to true. + */ +add_task(function* deprecated_API() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: IMAGE_PAGE, + }, function*(browser) { + yield pushPrefs([PREF_UNSAFE_FORBIDDEN, false]); + + let url = yield ContentTask.spawn(browser, null, function*() { + let image = content.document.getElementById("image"); + return image.href; + }); + + // Now get the document directly from content. If we run this test with + // e10s-enabled, this will be a CPOW, which is forbidden. We'll just + // pass the XUL document instead to test this interface. + let doc = document; + + saveImageURL(url, "image.jpg", null, true, false, null, doc, null, null); + yield waitForFilePicker(); + }); +}); diff --git a/toolkit/content/tests/browser/browser_save_resend_postdata.js b/toolkit/content/tests/browser/browser_save_resend_postdata.js new file mode 100644 index 0000000000..602a13d22b --- /dev/null +++ b/toolkit/content/tests/browser/browser_save_resend_postdata.js @@ -0,0 +1,145 @@ +/* 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/. */ + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +/** + * Test for bug 471962 <https://bugzilla.mozilla.org/show_bug.cgi?id=471962>: + * When saving an inner frame as file only, the POST data of the outer page is + * sent to the address of the inner page. + * + * Test for bug 485196 <https://bugzilla.mozilla.org/show_bug.cgi?id=485196>: + * Web page generated by POST is retried as GET when Save Frame As used, and the + * page is no longer in the cache. + */ +function test() { + waitForExplicitFinish(); + + gBrowser.loadURI("http://mochi.test:8888/browser/toolkit/content/tests/browser/data/post_form_outer.sjs"); + + gBrowser.addEventListener("pageshow", function pageShown(event) { + if (event.target.location == "about:blank") + return; + gBrowser.removeEventListener("pageshow", pageShown); + + // Submit the form in the outer page, then wait for both the outer + // document and the inner frame to be loaded again. + gBrowser.addEventListener("DOMContentLoaded", handleOuterSubmit); + gBrowser.contentDocument.getElementById("postForm").submit(); + }); + + var framesLoaded = 0; + var innerFrame; + + function handleOuterSubmit() { + if (++framesLoaded < 2) + return; + + gBrowser.removeEventListener("DOMContentLoaded", handleOuterSubmit); + + innerFrame = gBrowser.contentDocument.getElementById("innerFrame"); + + // Submit the form in the inner page. + gBrowser.addEventListener("DOMContentLoaded", handleInnerSubmit); + innerFrame.contentDocument.getElementById("postForm").submit(); + } + + function handleInnerSubmit() { + gBrowser.removeEventListener("DOMContentLoaded", handleInnerSubmit); + + // Create the folder the page will be saved into. + var destDir = createTemporarySaveDirectory(); + var file = destDir.clone(); + file.append("no_default_file_name"); + MockFilePicker.returnFiles = [file]; + MockFilePicker.showCallback = function(fp) { + MockFilePicker.filterIndex = 1; // kSaveAsType_URL + }; + + mockTransferCallback = onTransferComplete; + mockTransferRegisterer.register(); + + registerCleanupFunction(function () { + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + destDir.remove(true); + }); + + var docToSave = innerFrame.contentDocument; + // We call internalSave instead of saveDocument to bypass the history + // cache. + internalSave(docToSave.location.href, docToSave, null, null, + docToSave.contentType, false, null, null, + docToSave.referrer ? makeURI(docToSave.referrer) : null, + docToSave, false, null); + } + + function onTransferComplete(downloadSuccess) { + ok(downloadSuccess, "The inner frame should have been downloaded successfully"); + + // Read the entire saved file. + var file = MockFilePicker.returnFiles[0]; + var fileContents = readShortFile(file); + + // Check if outer POST data is found (bug 471962). + is(fileContents.indexOf("inputfield=outer"), -1, + "The saved inner frame does not contain outer POST data"); + + // Check if inner POST data is found (bug 485196). + isnot(fileContents.indexOf("inputfield=inner"), -1, + "The saved inner frame was generated using the correct POST data"); + + finish(); + } +} + +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this); + +function createTemporarySaveDirectory() { + var saveDir = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + return saveDir; +} + +/** + * Reads the contents of the provided short file (up to 1 MiB). + * + * @param aFile + * nsIFile object pointing to the file to be read. + * + * @return + * String containing the raw octets read from the file. + */ +function readShortFile(aFile) { + var inputStream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + inputStream.init(aFile, -1, 0, 0); + try { + var scrInputStream = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + scrInputStream.init(inputStream); + try { + // Assume that the file is much shorter than 1 MiB. + return scrInputStream.read(1048576); + } + finally { + // Close the scriptable stream after reading, even if the operation + // failed. + scrInputStream.close(); + } + } + finally { + // Close the stream after reading, if it is still open, even if the read + // operation failed. + inputStream.close(); + } +} diff --git a/toolkit/content/tests/browser/common/mockTransfer.js b/toolkit/content/tests/browser/common/mockTransfer.js new file mode 100644 index 0000000000..c8b8fc1616 --- /dev/null +++ b/toolkit/content/tests/browser/common/mockTransfer.js @@ -0,0 +1,67 @@ +/* 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/. */ + +Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader) + .loadSubScript("chrome://mochikit/content/tests/SimpleTest/MockObjects.js", this); + +var mockTransferCallback; + +/** + * This "transfer" object implementation continues the currently running test + * when the download is completed, reporting true for success or false for + * failure as the first argument of the testRunner.continueTest function. + */ +function MockTransfer() { + this._downloadIsSuccessful = true; +} + +MockTransfer.prototype = { + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIWebProgressListener, + Ci.nsIWebProgressListener2, + Ci.nsITransfer, + ]), + + /* nsIWebProgressListener */ + onStateChange: function MTFC_onStateChange(aWebProgress, aRequest, + aStateFlags, aStatus) { + // If at least one notification reported an error, the download failed. + if (!Components.isSuccessCode(aStatus)) + this._downloadIsSuccessful = false; + + // If the download is finished + if ((aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) && + (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) + // Continue the test, reporting the success or failure condition. + mockTransferCallback(this._downloadIsSuccessful); + }, + onProgressChange: function () {}, + onLocationChange: function () {}, + onStatusChange: function MTFC_onStatusChange(aWebProgress, aRequest, aStatus, + aMessage) { + // If at least one notification reported an error, the download failed. + if (!Components.isSuccessCode(aStatus)) + this._downloadIsSuccessful = false; + }, + onSecurityChange: function () {}, + + /* nsIWebProgressListener2 */ + onProgressChange64: function () {}, + onRefreshAttempted: function () {}, + + /* nsITransfer */ + init: function() {}, + setSha256Hash: function() {}, + setSignatureInfo: function() {} +}; + +// Create an instance of a MockObjectRegisterer whose methods can be used to +// temporarily replace the default "@mozilla.org/transfer;1" object factory with +// one that provides the mock implementation above. To activate the mock object +// factory, call the "register" method. Starting from that moment, all the +// transfer objects that are requested will be mock objects, until the +// "unregister" method is called. +var mockTransferRegisterer = + new MockObjectRegisterer("@mozilla.org/transfer;1", MockTransfer); diff --git a/toolkit/content/tests/browser/data/post_form_inner.sjs b/toolkit/content/tests/browser/data/post_form_inner.sjs new file mode 100644 index 0000000000..ce72159d81 --- /dev/null +++ b/toolkit/content/tests/browser/data/post_form_inner.sjs @@ -0,0 +1,31 @@ +/* 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/. */ + +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var body = + '<html>\ + <body>\ + Inner POST data: '; + + var bodyStream = new BinaryInputStream(request.bodyInputStream); + var bytes = [], avail = 0; + while ((avail = bodyStream.available()) > 0) + body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail)); + + body += + '<form id="postForm" action="post_form_inner.sjs" method="post">\ + <input type="text" name="inputfield" value="inner">\ + <input type="submit">\ + </form>\ + </body>\ + </html>'; + + response.bodyOutputStream.write(body, body.length); +} diff --git a/toolkit/content/tests/browser/data/post_form_outer.sjs b/toolkit/content/tests/browser/data/post_form_outer.sjs new file mode 100644 index 0000000000..89256fcfb2 --- /dev/null +++ b/toolkit/content/tests/browser/data/post_form_outer.sjs @@ -0,0 +1,34 @@ +/* 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/. */ + +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + var body = + '<html>\ + <body>\ + Outer POST data: '; + + var bodyStream = new BinaryInputStream(request.bodyInputStream); + var bytes = [], avail = 0; + while ((avail = bodyStream.available()) > 0) + body += String.fromCharCode.apply(String, bodyStream.readByteArray(avail)); + + body += + '<form id="postForm" action="post_form_outer.sjs" method="post">\ + <input type="text" name="inputfield" value="outer">\ + <input type="submit">\ + </form>\ + \ + <iframe id="innerFrame" src="post_form_inner.sjs" width="400" height="200">\ + \ + </body>\ + </html>'; + + response.bodyOutputStream.write(body, body.length); +} diff --git a/toolkit/content/tests/browser/empty.png b/toolkit/content/tests/browser/empty.png Binary files differnew file mode 100644 index 0000000000..17ddf0c3ee --- /dev/null +++ b/toolkit/content/tests/browser/empty.png diff --git a/toolkit/content/tests/browser/file_contentTitle.html b/toolkit/content/tests/browser/file_contentTitle.html new file mode 100644 index 0000000000..8d330aa0f2 --- /dev/null +++ b/toolkit/content/tests/browser/file_contentTitle.html @@ -0,0 +1,14 @@ +<html> +<head><title>Test Page</title></head> +<body> +<script type="text/javascript"> +dump("Script!\n"); +addEventListener("load", () => { + // Trigger an onLocationChange event. We want to make sure the title is still correct afterwards. + location.hash = "#x2"; + var event = new Event("TestLocationChange"); + document.dispatchEvent(event); +}, false); +</script> +</body> +</html> diff --git a/toolkit/content/tests/browser/file_mediaPlayback.html b/toolkit/content/tests/browser/file_mediaPlayback.html new file mode 100644 index 0000000000..5df0bc1542 --- /dev/null +++ b/toolkit/content/tests/browser/file_mediaPlayback.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<script type="text/javascript"> +var audio = new Audio(); +audio.oncanplay = function() { + audio.oncanplay = null; + audio.play(); +}; +audio.src = "audio.ogg"; +</script> diff --git a/toolkit/content/tests/browser/file_mediaPlayback2.html b/toolkit/content/tests/browser/file_mediaPlayback2.html new file mode 100644 index 0000000000..dffbd299b6 --- /dev/null +++ b/toolkit/content/tests/browser/file_mediaPlayback2.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<body> +<script type="text/javascript"> +var audio = new Audio(); +audio.oncanplay = function() { + audio.oncanplay = null; + audio.play(); +}; +audio.src = "audio.ogg"; +document.body.appendChild(audio); +</script> +</body> diff --git a/toolkit/content/tests/browser/file_mediaPlaybackFrame.html b/toolkit/content/tests/browser/file_mediaPlaybackFrame.html new file mode 100644 index 0000000000..119db62ecc --- /dev/null +++ b/toolkit/content/tests/browser/file_mediaPlaybackFrame.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<iframe src="file_mediaPlayback.html"></iframe> diff --git a/toolkit/content/tests/browser/file_mediaPlaybackFrame2.html b/toolkit/content/tests/browser/file_mediaPlaybackFrame2.html new file mode 100644 index 0000000000..d96a4cd4e9 --- /dev/null +++ b/toolkit/content/tests/browser/file_mediaPlaybackFrame2.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<iframe src="file_mediaPlayback2.html"></iframe> diff --git a/toolkit/content/tests/browser/file_multipleAudio.html b/toolkit/content/tests/browser/file_multipleAudio.html new file mode 100644 index 0000000000..5dc37febb4 --- /dev/null +++ b/toolkit/content/tests/browser/file_multipleAudio.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<audio id="autoplay" src="audio.ogg"></audio> +<audio id="nonautoplay" src="audio.ogg"></audio> +<script type="text/javascript"> + +// In linux debug on try server, sometimes the download process would fail, so +// we can't activate the "auto-play" or playing after receving "oncanplay". +// Therefore, we just call play here. +var audio = document.getElementById("autoplay"); +audio.loop = true; +audio.play(); + +</script> +</body> diff --git a/toolkit/content/tests/browser/file_multiplePlayingAudio.html b/toolkit/content/tests/browser/file_multiplePlayingAudio.html new file mode 100644 index 0000000000..ae122506fb --- /dev/null +++ b/toolkit/content/tests/browser/file_multiplePlayingAudio.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<audio id="audio1" src="audio.ogg" controls></audio> +<audio id="audio2" src="audio.ogg" controls></audio> +<script type="text/javascript"> + +// In linux debug on try server, sometimes the download process would fail, so +// we can't activate the "auto-play" or playing after receving "oncanplay". +// Therefore, we just call play here. +var audio1 = document.getElementById("audio1"); +audio1.loop = true; +audio1.play(); + +var audio2 = document.getElementById("audio2"); +audio2.loop = true; +audio2.play(); + +</script> +</body> diff --git a/toolkit/content/tests/browser/file_redirect.html b/toolkit/content/tests/browser/file_redirect.html new file mode 100644 index 0000000000..4d5fa9dfd1 --- /dev/null +++ b/toolkit/content/tests/browser/file_redirect.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>redirecting...</title> +<script> +window.addEventListener("load", + () => window.location = "file_redirect_to.html"); +</script> +<body> +redirectin u bro +</body> +</html> diff --git a/toolkit/content/tests/browser/file_redirect_to.html b/toolkit/content/tests/browser/file_redirect_to.html new file mode 100644 index 0000000000..28c0b53713 --- /dev/null +++ b/toolkit/content/tests/browser/file_redirect_to.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>redirected!</title> +<script> +window.addEventListener("load", () => { + var event = new Event("RedirectDone"); + document.dispatchEvent(event); +}); +</script> +<body> +u got redirected, bro +</body> +</html> diff --git a/toolkit/content/tests/browser/head.js b/toolkit/content/tests/browser/head.js new file mode 100644 index 0000000000..1c6c2b54f7 --- /dev/null +++ b/toolkit/content/tests/browser/head.js @@ -0,0 +1,33 @@ +"use strict"; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); + +/** + * A wrapper for the findbar's method "close", which is not synchronous + * because of animation. + */ +function closeFindbarAndWait(findbar) { + return new Promise((resolve) => { + if (findbar.hidden) { + resolve(); + return; + } + findbar.addEventListener("transitionend", function cont(aEvent) { + if (aEvent.propertyName != "visibility") { + return; + } + findbar.removeEventListener("transitionend", cont); + resolve(); + }); + findbar.close(); + }); +} + +function pushPrefs(...aPrefs) { + let deferred = Promise.defer(); + SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve); + return deferred.promise; +} diff --git a/toolkit/content/tests/browser/image.jpg b/toolkit/content/tests/browser/image.jpg Binary files differnew file mode 100644 index 0000000000..5031808ad2 --- /dev/null +++ b/toolkit/content/tests/browser/image.jpg diff --git a/toolkit/content/tests/browser/image_page.html b/toolkit/content/tests/browser/image_page.html new file mode 100644 index 0000000000..522a1d8cf9 --- /dev/null +++ b/toolkit/content/tests/browser/image_page.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>OHAI</title> +<body> +<img id="image" src="image.jpg" /> +</body> +</html> diff --git a/toolkit/content/tests/chrome/.eslintrc.js b/toolkit/content/tests/chrome/.eslintrc.js new file mode 100644 index 0000000000..2c669d844e --- /dev/null +++ b/toolkit/content/tests/chrome/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/toolkit/content/tests/chrome/RegisterUnregisterChrome.js b/toolkit/content/tests/chrome/RegisterUnregisterChrome.js new file mode 100644 index 0000000000..34f25d2f82 --- /dev/null +++ b/toolkit/content/tests/chrome/RegisterUnregisterChrome.js @@ -0,0 +1,161 @@ +/* This code is mostly copied from chrome/test/unit/head_crtestutils.js */ + +const NS_CHROME_MANIFESTS_FILE_LIST = "ChromeML"; +const XUL_CACHE_PREF = "nglayout.debug.disable_xul_cache"; + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +var gDirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIDirectoryService).QueryInterface(Ci.nsIProperties); +var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIXULChromeRegistry); +var gPrefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + +// Create the temporary file in the profile, instead of in TmpD, because +// we know the mochitest harness kills off the profile when it's done. +function copyToTemporaryFile(f) +{ + let tmpd = gDirSvc.get("ProfD", Ci.nsIFile); + tmpf = tmpd.clone(); + tmpf.append("temp.manifest"); + tmpf.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + tmpf.remove(false); + f.copyTo(tmpd, tmpf.leafName); + return tmpf; +} + +function* dirIter(directory) +{ + var ioSvc = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var testsDir = ioSvc.newURI(directory, null, null) + .QueryInterface(Ci.nsIFileURL).file; + + let en = testsDir.directoryEntries; + while (en.hasMoreElements()) { + let file = en.getNext(); + yield file.QueryInterface(Ci.nsIFile); + } +} + +function getParent(path) { + let lastSlash = path.lastIndexOf("/"); + if (lastSlash == -1) { + lastSlash = path.lastIndexOf("\\"); + if (lastSlash == -1) { + return ""; + } + return '/' + path.substring(0, lastSlash).replace(/\\/g, '/'); + } + return path.substring(0, lastSlash); +} + +function copyDirToTempProfile(path, subdirname) { + + if (subdirname === undefined) { + subdirname = "mochikit-tmp"; + } + + let tmpdir = gDirSvc.get("ProfD", Ci.nsIFile); + tmpdir.append(subdirname); + tmpdir.createUnique(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0o777); + + let rootDir = getParent(path); + if (rootDir == "") { + return tmpdir; + } + + // The SimpleTest directory is hidden + var files = Array.from(dirIter('file://' + rootDir)); + for (f in files) { + files[f].copyTo(tmpdir, ""); + } + return tmpdir; + +} + +function convertChromeURI(chromeURI) +{ + let uri = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService).newURI(chromeURI, null, null); + return gChromeReg.convertChromeURL(uri); +} + +function chromeURIToFile(chromeURI) +{ + var jar = getJar(chromeURI); + if (jar) { + var tmpDir = extractJarToTmp(jar); + let parts = chromeURI.split('/'); + if (parts[parts.length - 1] != '') { + tmpDir.append(parts[parts.length - 1]); + } + return tmpDir; + } + + return convertChromeURI(chromeURI). + QueryInterface(Ci.nsIFileURL).file; +} + +// Register a chrome manifest temporarily and return a function which un-does +// the registrarion when no longer needed. +function createManifestTemporarily(tempDir, manifestText) +{ + gPrefs.setBoolPref(XUL_CACHE_PREF, true); + + tempDir.append("temp.manifest"); + + let foStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + foStream.init(tempDir, + 0x02 | 0x08 | 0x20, 0o664, 0); // write, create, truncate + foStream.write(manifestText, manifestText.length); + foStream.close(); + let tempfile = copyToTemporaryFile(tempDir); + + Components.manager.QueryInterface(Ci.nsIComponentRegistrar). + autoRegister(tempfile); + + gChromeReg.refreshSkins(); + + return function() { + tempfile.fileSize = 0; // truncate the manifest + gChromeReg.checkForNewChrome(); + gChromeReg.refreshSkins(); + gPrefs.clearUserPref(XUL_CACHE_PREF); + } +} + +// Register a chrome manifest temporarily and return a function which un-does +// the registrarion when no longer needed. +function registerManifestTemporarily(manifestURI) +{ + gPrefs.setBoolPref(XUL_CACHE_PREF, true); + + let file = chromeURIToFile(manifestURI); + + let tempfile = copyToTemporaryFile(file); + Components.manager.QueryInterface(Ci.nsIComponentRegistrar). + autoRegister(tempfile); + + gChromeReg.refreshSkins(); + + return function() { + tempfile.fileSize = 0; // truncate the manifest + gChromeReg.checkForNewChrome(); + gChromeReg.refreshSkins(); + gPrefs.clearUserPref(XUL_CACHE_PREF); + } +} + +function registerManifestPermanently(manifestURI) +{ + var chromepath = chromeURIToFile(manifestURI); + + Components.manager.QueryInterface(Ci.nsIComponentRegistrar). + autoRegister(chromepath); + return chromepath; +} diff --git a/toolkit/content/tests/chrome/bug263683_window.xul b/toolkit/content/tests/chrome/bug263683_window.xul new file mode 100644 index 0000000000..46985a7adb --- /dev/null +++ b/toolkit/content/tests/chrome/bug263683_window.xul @@ -0,0 +1,210 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="263683test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="SimpleTest.executeSoon(startTest);" + title="263683 test"> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/AppConstants.jsm"); + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gPrefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + var gFindBar = null; + var gBrowser; + + var imports = ["SimpleTest", "ok", "info", "is"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function startTest() { + Task.spawn(function* () { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + yield startTestWithBrowser(browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(browserId) { + // We're bailing out when testing a remote browser on OSX 10.6, because it + // fails permanently. + if (browserId.endsWith("remote") && AppConstants.isPlatformAndVersionAtMost("macosx", 11)) { + return; + } + + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.loadURI('data:text/html,<h2>Text mozilla</h2><input id="inp" type="text" />'); + yield promise; + yield onDocumentLoaded(); + } + + function toggleHighlightAndWait(highlight) { + return new Promise(resolve => { + let listener = { + onHighlightFinished: function() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + gFindBar.toggleHighlight(highlight); + }); + } + + function* onDocumentLoaded() { + gFindBar.open(); + var search = "mozilla"; + gFindBar._findField.focus(); + gFindBar._findField.value = search; + var matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + matchCase.doCommand(); + } + + yield toggleHighlightAndWait(true); + gFindBar._find(); + + yield ContentTask.spawn(gBrowser, { search }, function* (args) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + Assert.ok("SELECTION_FIND" in controller, "Correctly detects new selection type"); + let selection = controller.getSelection(controller.SELECTION_FIND); + + Assert.equal(selection.rangeCount, 1, + "Correctly added a match to the selection type"); + Assert.equal(selection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + }); + + yield toggleHighlightAndWait(false); + + yield ContentTask.spawn(gBrowser, { search }, function* (args) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + let selection = controller.getSelection(controller.SELECTION_FIND); + Assert.equal(selection.rangeCount, 0, "Correctly removed the range"); + + let input = content.document.getElementById("inp"); + input.value = args.search; + }); + + yield toggleHighlightAndWait(true); + + yield ContentTask.spawn(gBrowser, { search }, function* (args) { + let input = content.document.getElementById("inp"); + let inputController = input.editor.selectionController; + let inputSelection = inputController.getSelection(inputController.SELECTION_FIND); + + Assert.equal(inputSelection.rangeCount, 1, + "Correctly added a match from input to the selection type"); + Assert.equal(inputSelection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + }); + + yield toggleHighlightAndWait(false); + + yield ContentTask.spawn(gBrowser, null, function* () { + let input = content.document.getElementById("inp"); + let inputController = input.editor.selectionController; + let inputSelection = inputController.getSelection(inputController.SELECTION_FIND); + + Assert.equal(inputSelection.rangeCount, 0, "Correctly removed the range"); + }); + + // For posterity, test iframes too. + + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.loadURI('data:text/html,<h2>Text mozilla</h2><iframe id="leframe" ' + + 'src="data:text/html,Text mozilla"></iframe>'); + yield promise; + + yield toggleHighlightAndWait(true); + + yield ContentTask.spawn(gBrowser, { search }, function* (args) { + function getSelection(docShell) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + return controller.getSelection(controller.SELECTION_FIND); + } + + let selection = getSelection(docShell); + Assert.equal(selection.rangeCount, 1, + "Correctly added a match to the selection type"); + Assert.equal(selection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + + // Check the iframe too: + let frame = content.document.getElementById("leframe"); + // Hoops! Get the docShell first, then the selection. + selection = getSelection(frame.contentWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell)); + Assert.equal(selection.rangeCount, 1, + "Correctly added a match to the selection type"); + Assert.equal(selection.getRangeAt(0).toString().toLowerCase(), + args.search, "Added the correct match"); + }); + + yield toggleHighlightAndWait(false); + + let matches = gFindBar._foundMatches.value.match(/([\d]*)\sof\s([\d]*)/); + is(matches[1], "2", "Found correct amount of matches") + + yield ContentTask.spawn(gBrowser, null, function* (args) { + function getSelection(docShell) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + return controller.getSelection(controller.SELECTION_FIND); + } + + let selection = getSelection(docShell); + Assert.equal(selection.rangeCount, 0, "Correctly removed the range"); + + // Check the iframe too: + let frame = content.document.getElementById("leframe"); + // Hoops! Get the docShell first, then the selection. + selection = getSelection(frame.contentWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell)); + Assert.equal(selection.rangeCount, 0, "Correctly removed the range"); + + content.document.documentElement.focus(); + }); + + gFindBar.close(true); + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug304188_window.xul b/toolkit/content/tests/chrome/bug304188_window.xul new file mode 100644 index 0000000000..931fd5c735 --- /dev/null +++ b/toolkit/content/tests/chrome/bug304188_window.xul @@ -0,0 +1,94 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="FindbarTest for bug 304188 - +find-menu appears in editor element which has had makeEditable() called but designMode not set"> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gFindBar = null; + var gBrowser; + + var imports = ["SimpleTest", "ok", "info"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function onLoad() { + Task.spawn(function* () { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + yield startTestWithBrowser(browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = ContentTask.spawn(gBrowser, null, function* () { + return new Promise(resolve => { + addEventListener("DOMContentLoaded", function listener() { + removeEventListener("DOMContentLoaded", listener); + resolve(); + }); + }); + }); + gBrowser.loadURI("data:text/html;charset=utf-8,some%20random%20text"); + yield promise; + yield onDocumentLoaded(); + } + + function* onDocumentLoaded() { + yield ContentTask.spawn(gBrowser, null, function* () { + var edsession = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIEditingSession); + edsession.makeWindowEditable(content, "html", false, true, false); + content.focus(); + }); + + yield enterStringIntoEditor("'"); + yield enterStringIntoEditor("/"); + + ok(gFindBar.hidden, + "Findfield should have stayed hidden after entering editor test"); + } + + function* enterStringIntoEditor(aString) { + for (let i = 0; i < aString.length; i++) { + yield ContentTask.spawn(gBrowser, { charCode: aString.charCodeAt(i) }, function* (args) { + let event = content.document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, args.charCode); + content.document.body.dispatchEvent(event); + }); + } + } + ]]></script> + + <browser id="content" flex="1" src="about:blank" type="content-primary"/> + <browser id="content-remote" remote="true" flex="1" src="about:blank" type="content-primary"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug331215_window.xul b/toolkit/content/tests/chrome/bug331215_window.xul new file mode 100644 index 0000000000..757ce61b8a --- /dev/null +++ b/toolkit/content/tests/chrome/bug331215_window.xul @@ -0,0 +1,102 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="331215test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="SimpleTest.executeSoon(startTest);" + title="331215 test"> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gFindBar = null; + var gBrowser; + + var imports = ["SimpleTest", "ok", "info"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + SimpleTest.requestLongerTimeout(2); + + function startTest() { + Task.spawn(function* () { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + yield startTestWithBrowser(browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.loadURI("data:text/plain,latest"); + yield promise; + yield onDocumentLoaded(); + } + + function* onDocumentLoaded() { + document.getElementById("cmd_find").doCommand(); + yield promiseEnterStringIntoFindField("test"); + document.commandDispatcher + .getControllerForCommand("cmd_moveTop") + .doCommand("cmd_moveTop"); + yield promiseEnterStringIntoFindField("l"); + ok(gFindBar._findField.getAttribute("status") == "notfound", + "Findfield status attribute should have been 'notfound' after entering test"); + yield promiseEnterStringIntoFindField("a"); + ok(gFindBar._findField.getAttribute("status") != "notfound", + "Findfield status attribute should not have been 'notfound' after entering latest"); + } + + function promiseEnterStringIntoFindField(aString) { + return new Promise(resolve => { + let listener = { + onFindResult: function(result) { + if (result.result == Ci.nsITypeAheadFind.FIND_FOUND && result.searchString != aString) + return; + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + + for (let c of aString) { + let code = c.charCodeAt(0); + let ev = new KeyboardEvent("keypress", { + keyCode: code, + charCode: code, + bubbles: true + }); + gFindBar._findField.inputField.dispatchEvent(ev); + } + }); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug360437_window.xul b/toolkit/content/tests/chrome/bug360437_window.xul new file mode 100644 index 0000000000..08498b58b2 --- /dev/null +++ b/toolkit/content/tests/chrome/bug360437_window.xul @@ -0,0 +1,120 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="360437Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="startTest();" + title="360437 test"> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gFindBar = null; + var gBrowser; + + var imports = ["SimpleTest", "ok", "is", "info"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function startTest() { + Task.spawn(function* () { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + yield startTestWithBrowser(browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = ContentTask.spawn(gBrowser, null, function* () { + return new Promise(resolve => { + addEventListener("DOMContentLoaded", function listener() { + removeEventListener("DOMContentLoaded", listener); + resolve(); + }); + }); + }); + gBrowser.loadURI("data:text/html,<form><input id='input' type='text' value='text inside an input element'></form>"); + yield promise; + yield onDocumentLoaded(); + } + + function* onDocumentLoaded() { + gFindBar.onFindCommand(); + + // Make sure the findfield is correctly focused on open + var searchStr = "text inside an input element"; + yield promiseEnterStringIntoFindField(searchStr); + is(document.commandDispatcher.focusedElement, + gFindBar._findField.inputField, "Find field isn't focused"); + + // Make sure "find again" correctly transfers focus to the content element + // when the find bar is closed. + gFindBar.close(); + gFindBar.onFindAgainCommand(false); + yield ContentTask.spawn(gBrowser, null, function* () { + Assert.equal(content.document.activeElement, + content.document.getElementById("input"), "Input Element isn't focused"); + }); + + // Make sure "find again" doesn't focus the content element if focus + // isn't in the content document. + var textbox = document.getElementById("textbox"); + textbox.focus(); + gFindBar.close(); + gFindBar.onFindAgainCommand(false); + ok(textbox.hasAttribute("focused"), + "Focus was stolen from a chrome element"); + } + + function promiseFindResult(str = null) { + return new Promise(resolve => { + let listener = { + onFindResult: function({ searchString }) { + if (str !== null && str != searchString) { + return; + } + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + function promiseEnterStringIntoFindField(str) { + let promise = promiseFindResult(str); + for (let i = 0; i < str.length; i++) { + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, str.charCodeAt(i)); + gFindBar._findField.inputField.dispatchEvent(event); + } + return promise; + } + ]]></script> + <textbox id="textbox"/> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug366992_window.xul b/toolkit/content/tests/chrome/bug366992_window.xul new file mode 100644 index 0000000000..a1e2ae1af1 --- /dev/null +++ b/toolkit/content/tests/chrome/bug366992_window.xul @@ -0,0 +1,57 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> + +<window id="366992 test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="onLoad();" + width="600" + height="600" + title="366992 test"> + + <commandset id="editMenuCommands"/> + + <script type="application/javascript" + src="chrome://global/content/globalOverlay.js"/> + <script type="application/javascript"><![CDATA[ + // Without the fix for bug 366992, the delete command would be enabled + // for the textbox even though the textbox's controller for this command + // disables it. + var gShouldNotBeReachedController = { + supportsCommand: function(aCommand) { + return aCommand == "cmd_delete"; + }, + isCommandEnabled: function(aCommand) { + return aCommand == "cmd_delete"; + }, + doCommand: function(aCommand) { } + } + + function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); + } + function finish() { + window.controllers.removeController(gShouldNotBeReachedController); + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad() { + document.getElementById("textbox").focus(); + var deleteDisabled = document.getElementById("cmd_delete") + .getAttribute("disabled") == "true"; + ok(deleteDisabled, + "cmd_delete should be disabled when the empty textbox is focused"); + finish(); + } + + window.controllers.appendController(gShouldNotBeReachedController); + ]]></script> + + <textbox id="textbox"/> +</window> diff --git a/toolkit/content/tests/chrome/bug409624_window.xul b/toolkit/content/tests/chrome/bug409624_window.xul new file mode 100644 index 0000000000..002cbe0421 --- /dev/null +++ b/toolkit/content/tests/chrome/bug409624_window.xul @@ -0,0 +1,98 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="409624test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + title="409624 test"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript"><![CDATA[ + var gFindBar = null; + var gBrowser; + + var imports = ["SimpleTest", "ok", "is"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function finish() { + window.close(); + SimpleTest.finish(); + } + + function startTest() { + gFindBar = document.getElementById("FindToolbar"); + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", onPageShow, false); + gBrowser.loadURI('data:text/html,<h2>Text mozilla</h2><input id="inp" type="text" />'); + } + + function onPageShow() { + gBrowser.removeEventListener("pageshow", onPageShow, false); + gFindBar.clear(); + let textbox = gFindBar.getElement("findbar-textbox"); + + // Clear should work regardless of whether the editor has been lazily + // initialised yet + ok(!gFindBar.hasTransactions, "No transactions when findbar empty"); + textbox.value = "mozilla"; + ok(gFindBar.hasTransactions, "Has transactions when findbar value set without editor init"); + gFindBar.clear(); + is(textbox.value, '', "findbar input value cleared after clear() call without editor init"); + ok(!gFindBar.hasTransactions, "No transactions after clear() call"); + + gFindBar.open(); + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden && matchCaseCheckbox.checked) + matchCaseCheckbox.click(); + ok(!matchCaseCheckbox.checked, "case-insensitivity correctly set"); + + // Simulate typical input + textbox.focus(); + gFindBar.clear(); + sendChar("m"); + ok(gFindBar.hasTransactions, "Has transactions after input"); + let preSelection = gBrowser.contentWindow.getSelection(); + ok(!preSelection.isCollapsed, "Found item and selected range"); + gFindBar.clear(); + is(textbox.value, '', "findbar input value cleared after clear() call"); + let postSelection = gBrowser.contentWindow.getSelection(); + ok(postSelection.isCollapsed, "item found deselected after clear() call"); + let fp = gFindBar.getElement("find-previous"); + ok(fp.disabled, "find-previous button disabled after clear() call"); + let fn = gFindBar.getElement("find-next"); + ok(fn.disabled, "find-next button disabled after clear() call"); + + // Test status updated after a search for text not in page + textbox.focus(); + sendChar("x"); + gFindBar.clear(); + let ftext = gFindBar.getElement("find-status"); + is(ftext.textContent, "", "status text disabled after clear() call"); + + // Test input empty with undo stack non-empty + textbox.focus(); + sendChar("m"); + sendKey("BACK_SPACE"); + ok(gFindBar.hasTransactions, "Has transactions when undo available"); + gFindBar.clear(); + gFindBar.close(); + + finish(); + } + + SimpleTest.waitForFocus(startTest, window); + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug429723_window.xul b/toolkit/content/tests/chrome/bug429723_window.xul new file mode 100644 index 0000000000..28439ae8e7 --- /dev/null +++ b/toolkit/content/tests/chrome/bug429723_window.xul @@ -0,0 +1,84 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="429723Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="429723 test"> + + <script type="application/javascript"><![CDATA[ + var gFindBar = null; + var gBrowser; + + function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); + } + + function finish() { + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad() { + var _delayedOnLoad = function() { + gFindBar = document.getElementById("FindToolbar"); + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", onPageShow, false); + gBrowser.loadURI("data:text/html,<h2 id='h2'>mozilla</h2>"); + } + setTimeout(_delayedOnLoad, 1000); + } + + function enterStringIntoFindField(aString) { + for (var i=0; i < aString.length; i++) { + var event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, aString.charCodeAt(i)); + gFindBar._findField.inputField.dispatchEvent(event); + } + } + + function onPageShow() { + gBrowser.removeEventListener("pageshow", onPageShow, false); + var findField = gFindBar._findField; + document.getElementById("cmd_find").doCommand(); + + var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden & matchCaseCheckbox.checked) + matchCaseCheckbox.click(); + + // Perform search + var searchStr = "z"; + enterStringIntoFindField(searchStr); + + // Highlight search term + var highlight = gFindBar.getElement("highlight"); + if (!highlight.checked) + highlight.click(); + + // Delete search term + var event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, KeyEvent.DOM_VK_BACK_SPACE, 0); + gFindBar._findField.inputField.dispatchEvent(event); + + var notRed = !findField.hasAttribute("status") || + (findField.getAttribute("status") != "notfound"); + ok(notRed, "Find Bar textbox is correct colour"); + finish(); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug451540_window.xul b/toolkit/content/tests/chrome/bug451540_window.xul new file mode 100644 index 0000000000..3c08c95c9f --- /dev/null +++ b/toolkit/content/tests/chrome/bug451540_window.xul @@ -0,0 +1,248 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"?> + +<window id="451540test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + title="451540 test"> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + const SEARCH_TEXT = "minefield"; + + let gFindBar = null; + let gPrefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + let gBrowser; + + let sendCtrl = true; + let sendMeta = false; + if (navigator.platform.indexOf("Mac") >= 0) { + sendCtrl = false; + sendMeta = true; + } + + let imports = [ "SimpleTest", "ok", "is", "info"]; + for (let name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + SimpleTest.requestLongerTimeout(2); + + function startTest() { + gFindBar = document.getElementById("FindToolbar"); + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", onPageShow, false); + let data = `data:text/html,<input id="inp" type="text" /> + <textarea id="tarea"/>`; + gBrowser.loadURI(data); + } + + function promiseHighlightFinished() { + return new Promise(resolve => { + let listener = { + onHighlightFinished() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + function* resetForNextTest(elementId, aText) { + if (!aText) + aText = SEARCH_TEXT; + + // Turn off highlighting + let highlightButton = gFindBar.getElement("highlight"); + if (highlightButton.checked) { + highlightButton.click(); + } + + // Initialise input + info(`setting element value to ${aText}`); + yield ContentTask.spawn(gBrowser, {elementId, aText}, function*(args) { + let {elementId, aText} = args; + let doc = content.document; + let element = doc.getElementById(elementId); + element.value = aText; + element.focus(); + }); + info(`just set element value to ${aText}`); + gFindBar._findField.value = SEARCH_TEXT; + + // Perform search and turn on highlighting + gFindBar._find(); + highlightButton.click(); + yield promiseHighlightFinished(); + + // Move caret to start of element + info(`focusing element`); + yield ContentTask.spawn(gBrowser, elementId, function*(elementId) { + let doc = content.document; + let element = doc.getElementById(elementId); + element.focus(); + }); + info(`focused element`); + if (navigator.platform.indexOf("Mac") >= 0) { + yield BrowserTestUtils.synthesizeKey("VK_LEFT", { metaKey: true }, gBrowser); + } else { + yield BrowserTestUtils.synthesizeKey("VK_HOME", {}, gBrowser); + } + } + + function* testSelection(elementId, expectedRangeCount, message) { + yield ContentTask.spawn(gBrowser, {elementId, expectedRangeCount, message}, function*(args) { + let {elementId, expectedRangeCount, message} = args; + let doc = content.document; + let element = doc.getElementById(elementId); + let controller = element.editor.selectionController; + let selection = controller.getSelection(controller.SELECTION_FIND); + Assert.equal(selection.rangeCount, expectedRangeCount, message); + }); + } + + function* testInput(elementId, testTypeText) { + let isEditableElement = yield ContentTask.spawn(gBrowser, elementId, function*(elementId) { + let doc = content.document; + let element = doc.getElementById(elementId); + return element instanceof Ci.nsIDOMNSEditableElement; + }); + if (!isEditableElement) { + return; + } + + // Initialize the findbar + let matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + matchCase.doCommand(); + } + + // First check match has been correctly highlighted + yield resetForNextTest(elementId); + + yield testSelection(elementId, 1, testTypeText + " correctly highlighted match"); + + // Test 2: check highlight removed when text added within the highlight + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + + yield testSelection(elementId, 0, testTypeText + " correctly removed highlight on text insertion"); + + // Test 3: check highlighting remains when text added before highlight + yield resetForNextTest(elementId); + yield BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + yield testSelection(elementId, 1, testTypeText + " highlight correctly remained on text insertion at start"); + + // Test 4: check highlighting remains when text added after highlight + yield resetForNextTest(elementId); + for (let x = 0; x < SEARCH_TEXT.length; x++) { + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + } + yield BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + yield testSelection(elementId, 1, testTypeText + " highlight correctly remained on text insertion at end"); + + // Test 5: deleting text within the highlight + yield resetForNextTest(elementId); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_BACK_SPACE", {}, gBrowser); + yield testSelection(elementId, 0, testTypeText + " correctly removed highlight on text deletion"); + + // Test 6: deleting text at end of highlight + yield resetForNextTest(elementId, SEARCH_TEXT + "A"); + for (let x = 0; x < (SEARCH_TEXT + "A").length; x++) { + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + } + yield BrowserTestUtils.synthesizeKey("VK_BACK_SPACE", {}, gBrowser); + yield testSelection(elementId, 1, testTypeText + " highlight correctly remained on text deletion at end"); + + // Test 7: deleting text at start of highlight + yield resetForNextTest(elementId, "A" + SEARCH_TEXT); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_BACK_SPACE", {}, gBrowser); + yield testSelection(elementId, 1, testTypeText + " highlight correctly remained on text deletion at start"); + + // Test 8: deleting selection + yield resetForNextTest(elementId); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { shiftKey: true }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { shiftKey: true }, gBrowser); + yield BrowserTestUtils.synthesizeKey("x", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield testSelection(elementId, 0, testTypeText + " correctly removed highlight on selection deletion"); + + // Test 9: Multiple matches within one editor (part 1) + // Check second match remains highlighted after inserting text into + // first match, and that its highlighting gets removed when the + // second match is edited + yield resetForNextTest(elementId, SEARCH_TEXT + " " + SEARCH_TEXT); + yield testSelection(elementId, 2, testTypeText + " correctly highlighted both matches"); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + yield testSelection(elementId, 1, testTypeText + " correctly removed only the first highlight on text insertion"); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_LEFT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("a", {}, gBrowser); + yield testSelection(elementId, 0, testTypeText + " correctly removed second highlight on text insertion"); + + // Test 10: Multiple matches within one editor (part 2) + // Check second match remains highlighted after deleting text in + // first match, and that its highlighting gets removed when the + // second match is edited + yield resetForNextTest(elementId, SEARCH_TEXT + " " + SEARCH_TEXT); + yield testSelection(elementId, 2, testTypeText + " correctly highlighted both matches"); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_BACK_SPACE", {}, gBrowser); + yield testSelection(elementId, 1, testTypeText + " correctly removed only the first highlight on text deletion"); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_LEFT", {}, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_BACK_SPACE", {}, gBrowser); + yield testSelection(elementId, 0, testTypeText + " correctly removed second highlight on text deletion"); + + // Test 11: Multiple matches within one editor (part 3) + // Check second match remains highlighted after deleting selection + // in first match, and that second match highlighting gets correctly + // removed when it has a selection deleted from it + yield resetForNextTest(elementId, SEARCH_TEXT + " " + SEARCH_TEXT); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { shiftKey: true }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { shiftKey: true }, gBrowser); + yield BrowserTestUtils.synthesizeKey("x", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield testSelection(elementId, 1, testTypeText + " correctly removed only first highlight on selection deletion"); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_RIGHT", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_LEFT", { shiftKey: true }, gBrowser); + yield BrowserTestUtils.synthesizeKey("VK_LEFT", { shiftKey: true }, gBrowser); + yield BrowserTestUtils.synthesizeKey("x", { ctrlKey: sendCtrl, metaKey: sendMeta }, gBrowser); + yield testSelection(elementId, 0, testTypeText + " correctly removed second highlight on selection deletion"); + } + + function onPageShow() { + gBrowser.removeEventListener("load", onPageShow, true); + Task.spawn(function*() { + gFindBar.open(); + yield testInput("inp", "Input:"); + yield testInput("tarea", "Textarea:"); + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + SimpleTest.waitForFocus(startTest, window); + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/bug624329_window.xul b/toolkit/content/tests/chrome/bug624329_window.xul new file mode 100644 index 0000000000..efca39d3bf --- /dev/null +++ b/toolkit/content/tests/chrome/bug624329_window.xul @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Test for bug 624329 context menu position" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + context="menu"> + + <script> + opener.SimpleTest.waitForFocus(opener.childFocused, window); + </script> + + <menupopup id="menu"> + <!-- The bug demonstrated only when the accesskey was presented separately + from the label. + e.g. because the accesskey is not a letter in the label. + + The bug demonstrates only on the first show of the context menu + unless menu items are removed/added each time the menu is + constructed. --> + <menuitem label="Long label to ensure the popup would hit the right of the screen" accesskey="1"/> + </menupopup> +</window> diff --git a/toolkit/content/tests/chrome/chrome.ini b/toolkit/content/tests/chrome/chrome.ini new file mode 100644 index 0000000000..2b9be4c8ea --- /dev/null +++ b/toolkit/content/tests/chrome/chrome.ini @@ -0,0 +1,199 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + ../widgets/popup_shared.js + ../widgets/tree_shared.js + RegisterUnregisterChrome.js + bug263683_window.xul + bug304188_window.xul + bug331215_window.xul + bug360437_window.xul + bug366992_window.xul + bug409624_window.xul + bug429723_window.xul + bug624329_window.xul + dialog_dialogfocus.xul + file_about_networking_wsh.py + file_autocomplete_with_composition.js + findbar_entireword_window.xul + findbar_events_window.xul + findbar_window.xul + frame_popup_anchor.xul + frame_popupremoving_frame.xul + frame_subframe_origin_subframe1.xul + frame_subframe_origin_subframe2.xul + popup_childframe_node.xul + popup_trigger.js + sample_entireword_latin1.html + window_browser_drop.xul + window_keys.xul + window_largemenu.xul + window_panel.xul + window_popup_anchor.xul + window_popup_anchoratrect.xul + window_popup_attribute.xul + window_popup_button.xul + window_popup_preventdefault_chrome.xul + window_preferences.xul + window_preferences2.xul + window_preferences3.xul + window_preferences_commandretarget.xul + window_screenPosSize.xul + window_showcaret.xul + window_subframe_origin.xul + window_titlebar.xul + window_tooltip.xul + xul_selectcontrol.js + rtlchrome/rtl.css + rtlchrome/rtl.dtd + rtlchrome/rtl.manifest + rtltest/righttoleft.manifest + rtltest/content/dirtest.xul + +[test_about_networking.html] +[test_arrowpanel.xul] +[test_autocomplete2.xul] +[test_autocomplete3.xul] +[test_autocomplete4.xul] +[test_autocomplete5.xul] +[test_autocomplete_delayOnPaste.xul] +subsuite = clipboard +[test_autocomplete_emphasis.xul] +[test_autocomplete_with_composition_on_input.html] +[test_autocomplete_with_composition_on_textbox.xul] +[test_autocomplete_placehold_last_complete.xul] +[test_browser_drop.xul] +[test_bug253481.xul] +subsuite = clipboard +[test_bug263683.xul] +[test_bug304188.xul] +[test_bug331215.xul] +[test_bug360220.xul] +[test_bug360437.xul] +skip-if = os == 'linux' # Bug 1264604 +[test_bug365773.xul] +[test_bug366992.xul] +[test_bug382990.xul] +[test_bug409624.xul] +[test_bug418874.xul] +[test_bug429723.xul] +[test_bug437844.xul] +[test_bug457632.xul] +[test_bug460942.xul] +[test_bug471776.xul] +[test_bug509732.xul] +[test_bug554279.xul] +[test_bug557987.xul] +[test_bug562554.xul] +[test_bug570192.xul] +[test_bug585946.xul] +[test_bug624329.xul] +skip-if = (os == 'mac' && os_version == '10.10') # Unexpectedly perma-passes on OSX 10.10 +[test_bug792324.xul] +[test_bug1048178.xul] +skip-if = toolkit == "cocoa" +[test_button.xul] +[test_closemenu_attribute.xul] +[test_colorpicker_popup.xul] +[test_contextmenu_list.xul] +[test_datepicker.xul] +[test_deck.xul] +[test_dialogfocus.xul] +[test_findbar.xul] +subsuite = clipboard +[test_findbar_entireword.xul] +[test_findbar_events.xul] +[test_focus_anons.xul] +[test_hiddenitems.xul] +[test_hiddenpaging.xul] +[test_keys.xul] +[test_labelcontrol.xul] +[test_largemenu.xul] +skip-if = os == 'linux' && !debug #Bug 1207174 +[test_menu.xul] +[test_menu_anchored.xul] +[test_menu_hide.xul] +[test_menuchecks.xul] +[test_menuitem_blink.xul] +[test_menuitem_commands.xul] +[test_menulist.xul] +[test_menulist_keynav.xul] +[test_menulist_null_value.xul] +[test_menulist_paging.xul] +[test_menulist_position.xul] +[test_mousescroll.xul] +[test_notificationbox.xul] +[test_panel.xul] +[test_panelfrommenu.xul] +[test_popup_anchor.xul] +[test_popup_anchoratrect.xul] +skip-if = os == 'linux' # 1167694 +[test_popup_attribute.xul] +skip-if = os == 'linux' && asan #Bug 1131634 +[test_popup_button.xul] +skip-if = os == 'linux' && asan # Bug 1281360 +[test_popup_coords.xul] +[test_popup_keys.xul] +[test_popup_moveToAnchor.xul] +[test_popup_preventdefault.xul] +[test_popup_preventdefault_chrome.xul] +[test_popup_recreate.xul] +[test_popup_scaled.xul] +[test_popup_tree.xul] +[test_popuphidden.xul] +[test_popupincontent.xul] +[test_popupremoving.xul] +[test_popupremoving_frame.xul] +[test_position.xul] +[test_preferences.xul] +[test_preferences_beforeaccept.xul] +support-files = window_preferences_beforeaccept.xul +[test_preferences_onsyncfrompreference.xul] +support-files = window_preferences_onsyncfrompreference.xul +[test_progressmeter.xul] +[test_props.xul] +[test_radio.xul] +[test_richlist_direction.xul] +[test_righttoleft.xul] +[test_scale.xul] +[test_scaledrag.xul] +[test_screenPersistence.xul] +[test_scrollbar.xul] +[test_showcaret.xul] +[test_sorttemplate.xul] +[test_statusbar.xul] +[test_subframe_origin.xul] +[test_tabbox.xul] +[test_tabindex.xul] +[test_textbox_dictionary.xul] +[test_textbox_emptytext.xul] +[test_textbox_number.xul] +[test_textbox_search.xul] +[test_timepicker.xul] +[test_titlebar.xul] +skip-if = os == "linux" +[test_toolbar.xul] +[test_tooltip.xul] +skip-if = (os == 'mac' && os_version == '10.10') # Bug 1141245, frequent timeouts on OSX 10.10 +[test_tooltip_noautohide.xul] +[test_tree.xul] +[test_tree_hier.xul] +[test_tree_hier_cell.xul] +[test_tree_single.xul] +[test_tree_view.xul] +# test_panel_focus.xul won't work if the Full Keyboard Access preference is set to +# textboxes and lists only, so skip this test on Mac +[test_panel_focus.xul] +support-files = window_panel_focus.xul +skip-if = toolkit == "cocoa" +[test_chromemargin.xul] +support-files = window_chromemargin.xul +skip-if = toolkit == "cocoa" +[test_bug451540.xul] +support-files = bug451540_window.xul +[test_autocomplete_mac_caret.xul] +skip-if = toolkit != "cocoa" +[test_cursorsnap.xul] +disabled = +#skip-if = os != "win" +support-files = window_cursorsnap_dialog.xul window_cursorsnap_wizard.xul diff --git a/toolkit/content/tests/chrome/dialog_dialogfocus.xul b/toolkit/content/tests/chrome/dialog_dialogfocus.xul new file mode 100644 index 0000000000..770695ed3e --- /dev/null +++ b/toolkit/content/tests/chrome/dialog_dialogfocus.xul @@ -0,0 +1,57 @@ +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<dialog buttons="extra2,accept,cancel" onload="loaded()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<tabbox id="tabbox" hidden="true"> + <tabs> + <tab id="tab" label="Tab"/> + </tabs> + <tabpanels> + <tabpanel> + <button id="tabbutton" label="Tab Button"/> + <button id="tabbutton2" label="Tab Button 2"/> + </tabpanel> + </tabpanels> +</tabbox> + +<textbox id="textbox-yes" value="textbox-yes" hidden="true"/> +<textbox id="textbox-no" value="textbox-no" noinitialfocus="true" hidden="true"/> +<button id="one" label="One"/> +<button id="two" label="Two" hidden="true"/> + +<script> +function loaded() +{ + if (window.arguments) { + var step = window.arguments[0]; + switch (step) { + case 2: + document.getElementById("one").setAttribute("noinitialfocus", "true"); + break; + case 3: + document.getElementById("one").hidden = true; + case 4: + document.getElementById("tabbutton2").setAttribute("noinitialfocus", "true"); + case 5: + document.getElementById("tabbutton").setAttribute("noinitialfocus", "true"); + case 6: + document.getElementById("tabbox").hidden = false; + break; + case 7: + var two = document.getElementById("two"); + two.hidden = false; + two.focus(); + break; + case 8: + document.getElementById("textbox-yes").hidden = false; + break; + case 9: + document.getElementById("textbox-no").hidden = false; + break; + } + } +} +</script> + +</dialog> diff --git a/toolkit/content/tests/chrome/file_about_networking_wsh.py b/toolkit/content/tests/chrome/file_about_networking_wsh.py new file mode 100644 index 0000000000..17ad250e56 --- /dev/null +++ b/toolkit/content/tests/chrome/file_about_networking_wsh.py @@ -0,0 +1,9 @@ +from mod_pywebsocket import msgutil + +def web_socket_do_extra_handshake(request): + pass + +def web_socket_transfer_data(request): + while not request.client_terminated: + msgutil.receive_message(request) + diff --git a/toolkit/content/tests/chrome/file_autocomplete_with_composition.js b/toolkit/content/tests/chrome/file_autocomplete_with_composition.js new file mode 100644 index 0000000000..881e772ad3 --- /dev/null +++ b/toolkit/content/tests/chrome/file_autocomplete_with_composition.js @@ -0,0 +1,540 @@ +// nsDoTestsForAutoCompleteWithComposition tests autocomplete with composition. +// Users must include SimpleTest.js and EventUtils.js. + +function waitForCondition(condition, nextTest) { + var tries = 0; + var interval = setInterval(function() { + if (condition() || tries >= 30) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function() { clearInterval(interval); nextTest(); }; +} + +function nsDoTestsForAutoCompleteWithComposition(aDescription, + aWindow, + aTarget, + aAutoCompleteController, + aIsFunc, + aGetTargetValueFunc, + aOnFinishFunc) +{ + this._description = aDescription; + this._window = aWindow; + this._target = aTarget; + this._controller = aAutoCompleteController; + + this._is = aIsFunc; + this._getTargetValue = aGetTargetValueFunc; + this._onFinish = aOnFinishFunc; + + this._target.focus(); + + this._DefaultCompleteDefaultIndex = + this._controller.input.completeDefaultIndex; + + this._doTests(); +} + +nsDoTestsForAutoCompleteWithComposition.prototype = { + _window: null, + _target: null, + _controller: null, + _DefaultCompleteDefaultIndex: false, + _description: "", + + _is: null, + _getTargetValue: function () { return "not initialized"; }, + _onFinish: null, + + _doTests: function () + { + if (++this._testingIndex == this._tests.length) { + this._controller.input.completeDefaultIndex = + this._DefaultCompleteDefaultIndex; + this._onFinish(); + return; + } + + var test = this._tests[this._testingIndex]; + if (this._controller.input.completeDefaultIndex != test.completeDefaultIndex) { + this._controller.input.completeDefaultIndex = test.completeDefaultIndex; + } + test.execute(this._window); + + waitForCondition(() => { + return this._controller.searchStatus >= + Components.interfaces.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH; + }, + this._checkResult.bind(this)); + }, + + _checkResult: function () + { + var test = this._tests[this._testingIndex]; + this._is(this._getTargetValue(), test.value, + this._description + ", " + test.description + ": value"); + this._is(this._controller.searchString, test.searchString, + this._description + ", " + test.description +": searchString"); + this._is(this._controller.input.popupOpen, test.popup, + this._description + ", " + test.description + ": popupOpen"); + this._doTests(); + }, + + _testingIndex: -1, + _tests: [ + // Simple composition when popup hasn't been shown. + // The autocomplete popup should not be shown during composition, but + // after compositionend, the popup should be shown. + { description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "M", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M, + shiftKey: true }, + }, aWindow); + }, popup: false, value: "M", searchString: "" + }, + { description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "Mo", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O }, + }, aWindow); + }, popup: false, value: "Mo", searchString: "" + }, + { description: "compositionend should open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Enter", code: "Enter" } }, aWindow); + }, popup: true, value: "Mo", searchString: "Mo" + }, + // If composition starts when popup is shown, the compositionstart event + // should cause closing the popup. + { description: "compositionstart should close the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "z", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z }, + }, aWindow); + }, popup: false, value: "Moz", searchString: "Mo" + }, + { description: "modifying composition string shouldn't reopen the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "zi", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "i", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I }, + }, aWindow); + }, popup: false, value: "Mozi", searchString: "Mo" + }, + { description: "compositionend should research the result and open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Enter", code: "Enter" } }, aWindow); + }, popup: true, value: "Mozi", searchString: "Mozi" + }, + // If composition is cancelled, the value shouldn't be changed. + { description: "compositionstart should reclose the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "l", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "l", code: "KeyL", keyCode: KeyboardEvent.DOM_VK_L }, + }, aWindow); + }, popup: false, value: "Mozil", searchString: "Mozi" + }, + { description: "modifying composition string shouldn't reopen the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "ll", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "l", code: "KeyL", keyCode: KeyboardEvent.DOM_VK_L }, + }, aWindow); + }, popup: false, value: "Mozill", searchString: "Mozi" + }, + { description: "modifying composition string to empty string shouldn't reopen the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 0, "length": 0 } + }, aWindow); + }, popup: false, value: "Mozi", searchString: "Mozi" + }, + { description: "cancled compositionend should reopen the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommit", data: "", + key: { key: "KEY_Escape", code: "Escape" } }, aWindow); + }, popup: true, value: "Mozi", searchString: "Mozi" + }, + // But if composition replaces some characters and canceled, the search + // string should be the latest value. + { description: "compositionstart with selected string should close the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow); + synthesizeKey("VK_LEFT", { shiftKey: true }, aWindow); + synthesizeCompositionChange( + { "composition": + { "string": "z", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z }, + }, aWindow); + }, popup: false, value: "Moz", searchString: "Mozi" + }, + { description: "modifying composition string shouldn't reopen the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "zi", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "i", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I }, + }, aWindow); + }, popup: false, value: "Mozi", searchString: "Mozi" + }, + { description: "modifying composition string to empty string shouldn't reopen the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 0, "length": 0 } + }, aWindow); + }, popup: false, value: "Mo", searchString: "Mozi" + }, + { description: "canceled compositionend should search the result with the latest value", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Escape", code: "Escape" } }, aWindow); + }, popup: true, value: "Mo", searchString: "Mo" + }, + // If all characters are removed, the popup should be closed. + { description: "the value becomes empty by backspace, the popup should be closed", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + }, popup: false, value: "", searchString: "" + }, + // composition which is canceled shouldn't cause opening the popup. + { description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "M", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "m", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M, + shiftKey: true }, + }, aWindow); + }, popup: false, value: "M", searchString: "" + }, + { description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "Mo", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O }, + }, aWindow); + }, popup: false, value: "Mo", searchString: "" + }, + { description: "modifying composition string to empty string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 0, "length": 0 } + }, aWindow); + }, popup: false, value: "", searchString: "" + }, + { description: "canceled compositionend shouldn't open the popup if it was closed", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Escape", code: "Escape" } }, aWindow); + }, popup: false, value: "", searchString: "" + }, + // Down key should open the popup even if the editor is empty. + { description: "DOWN key should open the popup even if the value is empty", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeKey("VK_DOWN", {}, aWindow); + }, popup: true, value: "", searchString: "" + }, + // If popup is open at starting composition, the popup should be reopened + // after composition anyway. + { description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "M", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M, + shiftKey: true }, + }, aWindow); + }, popup: false, value: "M", searchString: "" + }, + { description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "Mo", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O }, + }, aWindow); + }, popup: false, value: "Mo", searchString: "" + }, + { description: "modifying composition string to empty string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 0, "length": 0 } + }, aWindow); + }, popup: false, value: "", searchString: "" + }, + { description: "canceled compositionend should open the popup if it was opened", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Escape", code: "Escape" } }, aWindow); + }, popup: true, value: "", searchString: "" + }, + // Type normally, and hit escape, the popup should be closed. + { description: "ESCAPE should close the popup after typing something", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeKey("M", { shiftKey: true }, aWindow); + synthesizeKey("o", { shiftKey: true }, aWindow); + synthesizeKey("VK_ESCAPE", {}, aWindow); + }, popup: false, value: "Mo", searchString: "Mo" + }, + // Even if the popup is closed, composition which is canceled should open + // the popup if the value isn't empty. + // XXX This might not be good behavior, but anyway, this is minor issue... + { description: "compositionstart shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "z", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "z", code: "KeyZ", keyCode: KeyboardEvent.DOM_VK_Z }, + }, aWindow); + }, popup: false, value: "Moz", searchString: "Mo" + }, + { description: "modifying composition string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "zi", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "i", code: "KeyI", keyCode: KeyboardEvent.DOM_VK_I }, + }, aWindow); + }, popup: false, value: "Mozi", searchString: "Mo" + }, + { description: "modifying composition string to empty string shouldn't open the popup", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "", + "clauses": + [ + { "length": 0, "attr": 0 } + ] + }, + "caret": { "start": 0, "length": 0 } + }, aWindow); + }, popup: false, value: "Mo", searchString: "Mo" + }, + { description: "canceled compositionend shouldn't open the popup if the popup was closed", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Escape", code: "Escape" } }, aWindow); + }, popup: true, value: "Mo", searchString: "Mo" + }, + // House keeping... + { description: "house keeping for next tests", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + }, popup: false, value: "", searchString: "" + }, + // Testing for nsIAutoCompleteInput.completeDefaultIndex being true. + { description: "compositionstart shouldn't open the popup (completeDefaultIndex is true)", + completeDefaultIndex: true, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "M", + "clauses": + [ + { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 1, "length": 0 }, + "key": { key: "M", code: "KeyM", keyCode: KeyboardEvent.DOM_VK_M, + shiftKey: true }, + }, aWindow); + }, popup: false, value: "M", searchString: "" + }, + { description: "modifying composition string shouldn't open the popup (completeDefaultIndex is true)", + completeDefaultIndex: true, + execute: function (aWindow) { + synthesizeCompositionChange( + { "composition": + { "string": "Mo", + "clauses": + [ + { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": 2, "length": 0 }, + "key": { key: "o", code: "KeyO", keyCode: KeyboardEvent.DOM_VK_O }, + }, aWindow); + }, popup: false, value: "Mo", searchString: "" + }, + { description: "compositionend should open the popup (completeDefaultIndex is true)", + completeDefaultIndex: true, + execute: function (aWindow) { + synthesizeComposition({ type: "compositioncommitasis", + key: { key: "KEY_Enter", code: "Enter" } }, aWindow); + }, popup: true, value: "Mozilla", searchString: "Mo" + }, + // House keeping... + { description: "house keeping for next tests", + completeDefaultIndex: false, + execute: function (aWindow) { + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + synthesizeKey("VK_BACK_SPACE", {}, aWindow); + }, popup: false, value: "", searchString: "" + } + ] +}; diff --git a/toolkit/content/tests/chrome/findbar_entireword_window.xul b/toolkit/content/tests/chrome/findbar_entireword_window.xul new file mode 100644 index 0000000000..f0da61081d --- /dev/null +++ b/toolkit/content/tests/chrome/findbar_entireword_window.xul @@ -0,0 +1,275 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"?> + +<window id="FindbarEntireWordTest" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="findbar test - entire words only"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"/> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gFindBar = null; + var gBrowser; + + var imports = ["SimpleTest", "SpecialPowers", "ok", "is", "isnot", "info"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + SimpleTest.requestLongerTimeout(2); + + const kBaseURL = "chrome://mochitests/content/chrome/toolkit/content/tests/chrome"; + const kTests = { + latin1: { + testSimpleEntireWord: { + "and": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'and' should've been found"); + is(results.matches.total, 6, "should've found 6 matches"); + }, + "an": results => { + is(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'an' shouldn't have been found"); + is(results.matches.total, 0, "should've found 0 matches"); + }, + "darkness": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'darkness' should've been found"); + is(results.matches.total, 3, "should've found 3 matches"); + }, + "mammon": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'mammon' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + }, + testCaseSensitive: { + "And": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'And' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + }, + "and": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'and' should've been found"); + is(results.matches.total, 5, "should've found 5 matches"); + }, + "Mammon": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'mammon' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + }, + testWordBreakChars: { + "a": results => { + // 'a' is a common charactar, but there should only be one occurrence + // separated by word boundaries. + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'a' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + }, + "quarrelled": results => { + // 'quarrelled' is denoted as a word by a period char. + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'quarrelled' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + }, + testQuickfind: { + "and": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'and' should've been found"); + is(results.matches.total, 6, "should've found 6 matches"); + }, + "an": results => { + is(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'an' shouldn't have been found"); + is(results.matches.total, 0, "should've found 0 matches"); + }, + "darkness": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'darkness' should've been found"); + is(results.matches.total, 3, "should've found 3 matches"); + }, + "mammon": results => { + isnot(results.find.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "'mammon' should've been found"); + is(results.matches.total, 1, "should've found 1 match"); + } + } + } + }; + + function onLoad() { + Task.spawn(function* () { + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv( + { set: [["findbar.entireword", true]] }, resolve); + }); + + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + // XXXmikedeboer: when multiple test samples are available, make this + // a nested loop that iterates over them. For now, only + // latin is available. + yield startTestWithBrowser("latin1", browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(testName, browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + + let promise = ContentTask.spawn(gBrowser, null, function* () { + return new Promise(resolve => { + addEventListener("DOMContentLoaded", function listener() { + removeEventListener("DOMContentLoaded", listener); + resolve(); + }); + }); + }); + gBrowser.loadURI(kBaseURL + "/sample_entireword_" + testName + ".html"); + yield promise; + yield onDocumentLoaded(testName); + } + + function* onDocumentLoaded(testName) { + let suite = kTests[testName]; + yield testSimpleEntireWord(suite.testSimpleEntireWord); + yield testCaseSensitive(suite.testCaseSensitive); + yield testWordBreakChars(suite.testWordBreakChars); + yield testQuickfind(suite.testQuickfind); + } + + var enterStringIntoFindField = Task.async(function* (str, waitForResult = true) { + for (let promise, i = 0; i < str.length; i++) { + if (waitForResult) { + promise = promiseFindResult(); + } + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, str.charCodeAt(i)); + gFindBar._findField.inputField.dispatchEvent(event); + if (waitForResult) { + yield promise; + } + } + }); + + function openFindbar() { + document.getElementById("cmd_find").doCommand(); + return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise; + } + + function promiseFindResult(searchString) { + return new Promise(resolve => { + let data = {}; + let listener = { + onFindResult: res => { + if (searchString && res.searchString != searchString) + return; + + gFindBar.browser.finder.removeResultListener(listener); + data.find = res; + if (res.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) { + data.matches = { total: 0, current: 0 }; + resolve(data); + return; + } + listener = { + onMatchesCountResult: res => { + gFindBar.browser.finder.removeResultListener(listener); + data.matches = res; + resolve(data); + } + }; + gFindBar.browser.finder.addResultListener(listener); + } + }; + + gFindBar.browser.finder.addResultListener(listener); + }); + } + + function* testIterator(tests) { + for (let searchString of Object.getOwnPropertyNames(tests)) { + gFindBar.clear(); + + let promise = promiseFindResult(searchString); + + yield enterStringIntoFindField(searchString, false); + + let result = yield promise; + tests[searchString](result); + } + } + + function* testSimpleEntireWord(tests) { + yield openFindbar(); + ok(!gFindBar.hidden, "testSimpleEntireWord: findbar should be open"); + + yield* testIterator(tests); + + gFindBar.close(); + } + + function* testCaseSensitive(tests) { + yield openFindbar(); + ok(!gFindBar.hidden, "testCaseSensitive: findbar should be open"); + + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden && !matchCaseCheckbox.checked) + matchCaseCheckbox.click(); + + yield* testIterator(tests); + + if (!matchCaseCheckbox.hidden) + matchCaseCheckbox.click(); + gFindBar.close(); + } + + function* testWordBreakChars(tests) { + yield openFindbar(); + ok(!gFindBar.hidden, "testWordBreakChars: findbar should be open"); + + yield* testIterator(tests); + + gFindBar.close(); + } + + function* testQuickfind(tests) { + yield ContentTask.spawn(gBrowser, null, function* () { + let document = content.document; + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, "/".charCodeAt(0)); + document.documentElement.dispatchEvent(event); + }); + + ok(!gFindBar.hidden, "testQuickfind: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testQuickfind: find field is not focused"); + ok(!gFindBar.getElement("entire-word-status").hidden, + "testQuickfind: entire word mode status text should be visible"); + + yield* testIterator(tests); + + gFindBar.close(); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/findbar_events_window.xul b/toolkit/content/tests/chrome/findbar_events_window.xul new file mode 100644 index 0000000000..2bfc52c146 --- /dev/null +++ b/toolkit/content/tests/chrome/findbar_events_window.xul @@ -0,0 +1,173 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="FindbarTest" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="SimpleTest.executeSoon(startTest);" + title="findbar events test"> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gFindBar = null; + var gBrowser; + const kTimeout = 5000; // 5 seconds. + + var imports = ["SimpleTest", "ok", "is", "info"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + SimpleTest.requestLongerTimeout(2); + + function startTest() { + Task.spawn(function* () { + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + yield startTestWithBrowser(browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.loadURI("data:text/html,hello there"); + yield promise; + yield onDocumentLoaded(); + } + + function* onDocumentLoaded() { + gFindBar.open(); + gFindBar.onFindCommand(); + + yield testFind(); + yield testFindAgain(); + yield testCaseSensitivity(); + yield testHighlight(); + } + + function checkSelection() { + return new Promise(resolve => { + SimpleTest.executeSoon(() => { + ContentTask.spawn(gBrowser, null, function* () { + let selected = content.getSelection(); + Assert.equal(String(selected), "", "No text is selected"); + + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + let selection = controller.getSelection(controller.SELECTION_FIND); + Assert.equal(selection.rangeCount, 0, "No text is highlighted"); + }).then(resolve); + }); + }); + } + + function once(node, eventName, preventDefault = true) { + return new Promise((resolve, reject) => { + let timeout = window.setTimeout(() => { + reject("Event wasn't fired within " + kTimeout + "ms for event '" + + eventName + "'."); + }, kTimeout); + + node.addEventListener(eventName, function clb(e) { + window.clearTimeout(timeout); + node.removeEventListener(eventName, clb); + if (preventDefault) + e.preventDefault(); + resolve(e); + }); + }); + } + + function* testFind() { + info("Testing normal find."); + let query = "t"; + let promise = once(gFindBar, "find"); + + // Put some text in the find box. + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, query.charCodeAt(0)); + gFindBar._findField.inputField.dispatchEvent(event); + + let e = yield promise; + ok(e.detail.query === query, "find event query should match '" + query + "'"); + // Since we're preventing the default make sure nothing was selected. + yield checkSelection(); + } + + function testFindAgain() { + info("Testing repeating normal find."); + let promise = once(gFindBar, "findagain"); + + gFindBar.onFindAgainCommand(); + + yield promise; + // Since we're preventing the default make sure nothing was selected. + yield checkSelection(); + } + + function* testCaseSensitivity() { + info("Testing normal case sensitivity."); + let promise = once(gFindBar, "findcasesensitivitychange", false); + + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + matchCaseCheckbox.click(); + + let e = yield promise; + ok(e.detail.caseSensitive, "find should be case sensitive"); + + // Toggle it back to the original setting. + matchCaseCheckbox.click(); + + // Changing case sensitivity does the search so clear the selected text + // before the next test. + yield ContentTask.spawn(gBrowser, null, () => content.getSelection().removeAllRanges()); + } + + function* testHighlight() { + info("Testing find with highlight all."); + // Update the find state so the highlight button is clickable. + gFindBar.updateControlState(Ci.nsITypeAheadFind.FIND_FOUND, false); + + let promise = once(gFindBar, "findhighlightallchange"); + + let highlightButton = gFindBar.getElement("highlight"); + if (!highlightButton.checked) + highlightButton.click(); + + let e = yield promise; + ok(e.detail.highlightAll, "find event should have highlight all set"); + // Since we're preventing the default make sure nothing was highlighted. + yield checkSelection(); + + // Toggle it back to the original setting. + if (highlightButton.checked) + highlightButton.click(); + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/findbar_window.xul b/toolkit/content/tests/chrome/findbar_window.xul new file mode 100644 index 0000000000..f17f760fe0 --- /dev/null +++ b/toolkit/content/tests/chrome/findbar_window.xul @@ -0,0 +1,756 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window id="FindbarTest" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="findbar test"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <script type="application/javascript"><![CDATA[ + const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components; + Cu.import("resource://gre/modules/AppConstants.jsm"); + Cu.import("resource://gre/modules/Task.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + Cu.import("resource://testing-common/ContentTask.jsm"); + ContentTask.setTestScope(window.opener.wrappedJSObject); + + var gPrefsvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + + const SAMPLE_URL = "http://www.mozilla.org/"; + const SAMPLE_TEXT = "Some text in a text field."; + const SEARCH_TEXT = "Text Test"; + const NOT_FOUND_TEXT = "This text is not on the page." + const ITERATOR_TIMEOUT = gPrefsvc.getIntPref("findbar.iteratorTimeout"); + + var gFindBar = null; + var gBrowser; + + var gClipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard); + var gHasFindClipboard = gClipboard.supportsFindClipboard(); + + var gStatusText; + var gXULBrowserWindow = { + QueryInterface: function(aIID) { + if (aIID.Equals(Ci.nsIXULBrowserWindow) || + aIID.Equals(Ci.nsISupports)) + return this; + + throw Cr.NS_NOINTERFACE; + }, + + setJSStatus: function() { }, + + setOverLink: function(aStatusText, aLink) { + gStatusText = aStatusText; + }, + + onBeforeLinkTraversal: function() { } + }; + + var imports = ["SimpleTest", "ok", "is", "info"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + SimpleTest.requestLongerTimeout(2); + + function onLoad() { + Task.spawn(function* () { + window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .XULBrowserWindow = gXULBrowserWindow; + + gFindBar = document.getElementById("FindToolbar"); + for (let browserId of ["content", "content-remote"]) { + yield startTestWithBrowser(browserId); + } + }).then(() => { + window.close(); + SimpleTest.finish(); + }); + } + + function* startTestWithBrowser(browserId) { + info("Starting test with browser '" + browserId + "'"); + gBrowser = document.getElementById(browserId); + gFindBar.browser = gBrowser; + + // Tests delays the loading of a document for one second. + yield new Promise(resolve => setTimeout(resolve, 1000)); + + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.loadURI("data:text/html,<h2 id='h2'>" + SEARCH_TEXT + + "</h2><h2><a href='" + SAMPLE_URL + "'>Link Test</a></h2><input id='text' type='text' value='" + + SAMPLE_TEXT + "'></input><input id='button' type='button'></input><img id='img' width='50' height='50'/>"); + yield promise; + yield onDocumentLoaded(); + } + + function* onDocumentLoaded() { + yield testNormalFind(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testNormalFind"); + yield openFindbar(); + yield testNormalFindWithComposition(); + gFindBar.close(); + ok(gFindBar.hidden, "findbar should be hidden after testNormalFindWithComposition"); + yield openFindbar(); + yield testAutoCaseSensitivityUI(); + yield testQuickFindText(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testQuickFindText"); + // TODO: `testFindWithHighlight` tests fastFind integrity, which can not + // be accessed with RemoteFinder. We're skipping it for now. + if (gFindBar._browser.finder._fastFind) { + yield testFindWithHighlight(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testFindWithHighlight"); + } + yield testFindbarSelection(); + ok(gFindBar.hidden, "Failed to close findbar after testFindbarSelection"); + // TODO: I don't know how to drop a content element on a chrome input. + if (!gBrowser.hasAttribute("remote")) + testDrop(); + yield testQuickFindLink(); + if (gHasFindClipboard) { + yield testStatusText(); + } + + if (!AppConstants.DEBUG) { + yield testFindCountUI(); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI"); + yield testFindCountUI(true); + gFindBar.close(); + ok(gFindBar.hidden, "Failed to close findbar after testFindCountUI - linksOnly"); + } + + yield openFindbar(); + yield testFindAfterCaseChanged(); + gFindBar.close(); + yield openFindbar(); + yield testFailedStringReset(); + gFindBar.close(); + yield testQuickFindClose(); + // TODO: This doesn't seem to work when the findbar is connected to a + // remote browser element. + if (!gBrowser.hasAttribute("remote")) + yield testFindAgainNotFound(); + yield testToggleEntireWord(); + } + + function* testFindbarSelection() { + function checkFindbarState(aTestName, aExpSelection) { + ok(!gFindBar.hidden, "testFindbarSelection: failed to open findbar: " + aTestName); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testFindbarSelection: find field is not focused: " + aTestName); + if (!gHasFindClipboard) { + ok(gFindBar._findField.value == aExpSelection, + "Incorrect selection in testFindbarSelection: " + aTestName + + ". Selection: " + gFindBar._findField.value); + } + + // Clear the value, close the findbar. + gFindBar._findField.value = ""; + gFindBar.close(); + } + + // Test normal selected text. + yield ContentTask.spawn(gBrowser, null, function* () { + let document = content.document; + let cH2 = document.getElementById("h2"); + let cSelection = content.getSelection(); + let cRange = document.createRange(); + cRange.setStart(cH2, 0); + cRange.setEnd(cH2, 1); + cSelection.removeAllRanges(); + cSelection.addRange(cRange); + }); + yield openFindbar(); + checkFindbarState("plain text", SEARCH_TEXT); + + // Test nsIDOMNSEditableElement with selection. + yield ContentTask.spawn(gBrowser, null, function* () { + let textInput = content.document.getElementById("text"); + textInput.focus(); + textInput.select(); + }); + yield openFindbar(); + checkFindbarState("text input", SAMPLE_TEXT); + + // Test non-editable nsIDOMNSEditableElement (button). + yield ContentTask.spawn(gBrowser, null, function* () { + content.document.getElementById("button").focus(); + }); + yield openFindbar(); + checkFindbarState("button", ""); + } + + function testDrop() { + gFindBar.open(); + // use an dummy image to start the drag so it doesn't get interrupted by a selection + var img = gBrowser.contentDocument.getElementById("img"); + synthesizeDrop(img, gFindBar._findField, [[ {type: "text/plain", data: "Rabbits" } ]], "copy", window); + is(gFindBar._findField.inputField.value, "Rabbits", "drop on findbar"); + gFindBar.close(); + } + + function testQuickFindClose() { + return new Promise(resolve => { + var _isClosedCallback = function() { + ok(gFindBar.hidden, + "_isClosedCallback: Failed to auto-close quick find bar after " + + gFindBar._quickFindTimeoutLength + "ms"); + resolve(); + }; + setTimeout(_isClosedCallback, gFindBar._quickFindTimeoutLength + 100); + }); + } + + function testStatusText() { + return new Promise(resolve => { + var _delayedCheckStatusText = function() { + ok(gStatusText == SAMPLE_URL, "testStatusText: Failed to set status text of found link"); + resolve(); + }; + setTimeout(_delayedCheckStatusText, 100); + }); + } + + function promiseFindResult() { + return new Promise(resolve => { + let listener = { + onFindResult: function(result) { + gFindBar.browser.finder.removeResultListener(listener); + resolve(result); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + function promiseMatchesCountResult() { + return new Promise(resolve => { + let listener = { + onMatchesCountResult: function() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + // Make sure we resolve _at least_ after five times the find iterator timeout. + setTimeout(resolve, (ITERATOR_TIMEOUT * 5) + 20); + }); + } + + function promiseHighlightFinished() { + return new Promise(resolve => { + let listener = { + onHighlightFinished() { + gFindBar.browser.finder.removeResultListener(listener); + resolve(); + } + }; + gFindBar.browser.finder.addResultListener(listener); + }); + } + + var enterStringIntoFindField = Task.async(function* (str, waitForResult = true) { + for (let promise, i = 0; i < str.length; i++) { + if (waitForResult) { + promise = promiseFindResult(); + } + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, str.charCodeAt(i)); + gFindBar._findField.inputField.dispatchEvent(event); + if (waitForResult) { + yield promise; + } + } + }); + + function promiseExpectRangeCount(rangeCount) { + return ContentTask.spawn(gBrowser, { rangeCount }, function* (args) { + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND); + Assert.equal(sel.rangeCount, args.rangeCount, + "Expected the correct amount of ranges inside the Find selection"); + }); + } + + // also test match-case + function* testNormalFind() { + document.getElementById("cmd_find").doCommand(); + + ok(!gFindBar.hidden, "testNormalFind: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testNormalFind: find field is not focused"); + + let promise; + let matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + if (!matchCaseCheckbox.hidden && matchCaseCheckbox.checked) { + promise = promiseFindResult(); + matchCaseCheckbox.click(); + yield promise; + } + + var searchStr = "text tes"; + yield enterStringIntoFindField(searchStr); + + let sel = yield ContentTask.spawn(gBrowser, { searchStr }, function* (args) { + let sel = content.getSelection().toString(); + Assert.equal(sel.toLowerCase(), args.searchStr, + "testNormalFind: failed to find '" + args.searchStr + "'"); + return sel; + }); + testClipboardSearchString(sel); + + if (!matchCaseCheckbox.hidden) { + promise = promiseFindResult(); + matchCaseCheckbox.click(); + yield promise; + enterStringIntoFindField("t"); + yield ContentTask.spawn(gBrowser, { searchStr }, function* (args) { + Assert.notEqual(content.getSelection().toString(), args.searchStr, + "testNormalFind: Case-sensitivy is broken '" + args.searchStr + "'"); + }); + promise = promiseFindResult(); + matchCaseCheckbox.click(); + yield promise; + } + } + + function openFindbar() { + document.getElementById("cmd_find").doCommand(); + return gFindBar._startFindDeferred && gFindBar._startFindDeferred.promise; + } + + function* testNormalFindWithComposition() { + ok(!gFindBar.hidden, "testNormalFindWithComposition: findbar should be open"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testNormalFindWithComposition: find field should be focused"); + + var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + var clicked = false; + if (!matchCaseCheckbox.hidden & matchCaseCheckbox.checked) { + matchCaseCheckbox.click(); + clicked = true; + } + + gFindBar._findField.inputField.focus(); + + var searchStr = "text"; + + synthesizeCompositionChange( + { "composition": + { "string": searchStr, + "clauses": + [ + { "length": searchStr.length, "attr": COMPOSITION_ATTR_RAW_CLAUSE } + ] + }, + "caret": { "start": searchStr.length, "length": 0 } + }); + + yield ContentTask.spawn(gBrowser, { searchStr }, function* (args) { + Assert.notEqual(content.getSelection().toString().toLowerCase(), args.searchStr, + "testNormalFindWithComposition: text shouldn't be found during composition"); + }); + + synthesizeComposition({ type: "compositioncommitasis" }); + + let sel = yield ContentTask.spawn(gBrowser, { searchStr }, function* (args) { + let sel = content.getSelection().toString(); + Assert.equal(sel.toLowerCase(), args.searchStr, + "testNormalFindWithComposition: text should be found after committing composition"); + return sel; + }); + testClipboardSearchString(sel); + + if (clicked) { + matchCaseCheckbox.click(); + } + } + + function* testAutoCaseSensitivityUI() { + var matchCaseCheckbox = gFindBar.getElement("find-case-sensitive"); + var matchCaseLabel = gFindBar.getElement("match-case-status"); + ok(!matchCaseCheckbox.hidden, "match case box is hidden in manual mode"); + ok(matchCaseLabel.hidden, "match case label is visible in manual mode"); + + gPrefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 2); + + ok(matchCaseCheckbox.hidden, + "match case box is visible in automatic mode"); + ok(!matchCaseLabel.hidden, + "match case label is hidden in automatic mode"); + + yield enterStringIntoFindField("a"); + var insensitiveLabel = matchCaseLabel.value; + yield enterStringIntoFindField("A"); + var sensitiveLabel = matchCaseLabel.value; + ok(insensitiveLabel != sensitiveLabel, + "Case Sensitive label was not correctly updated"); + + // bug 365551 + gFindBar.onFindAgainCommand(); + ok(matchCaseCheckbox.hidden && !matchCaseLabel.hidden, + "bug 365551: case sensitivity UI is broken after find-again"); + gPrefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0); + gFindBar.close(); + } + + function* clearFocus() { + document.commandDispatcher.focusedElement = null; + document.commandDispatcher.focusedWindow = null; + yield ContentTask.spawn(gBrowser, null, function* () { + content.focus(); + }); + } + + function* testQuickFindLink() { + yield clearFocus(); + + yield ContentTask.spawn(gBrowser, null, function* () { + let document = content.document; + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, "'".charCodeAt(0)); + document.documentElement.dispatchEvent(event); + }); + + ok(!gFindBar.hidden, "testQuickFindLink: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testQuickFindLink: find field is not focused"); + + var searchStr = "Link Test"; + yield enterStringIntoFindField(searchStr); + yield ContentTask.spawn(gBrowser, { searchStr }, function* (args) { + Assert.equal(content.getSelection().toString(), args.searchStr, + "testQuickFindLink: failed to find sample link"); + }); + testClipboardSearchString(searchStr); + } + + // See bug 963925 for more details on this test. + function* testFindWithHighlight() { + gFindBar._findField.value = ""; + + // For this test, we want to closely control the selection. The easiest + // way to do so is to replace the implementation of + // Finder.getInitialSelection with a no-op and call the findbar's callback + // (onCurrentSelection(..., true)) ourselves with our hand-picked + // selection. + let oldGetInitialSelection = gFindBar.browser.finder.getInitialSelection; + let searchStr; + gFindBar.browser.finder.getInitialSelection = function(){}; + + let findCommand = document.getElementById("cmd_find"); + findCommand.doCommand(); + + gFindBar.onCurrentSelection("", true); + + searchStr = "e"; + yield enterStringIntoFindField(searchStr); + + let a = gFindBar._findField.value; + let b = gFindBar._browser.finder._fastFind.searchString; + let c = gFindBar._browser.finder.searchString; + ok(a == b && b == c, "testFindWithHighlight 1: " + a + ", " + b + ", " + c + "."); + + searchStr = "t"; + findCommand.doCommand(); + + gFindBar.onCurrentSelection(searchStr, true); + gFindBar.browser.finder.getInitialSelection = oldGetInitialSelection; + + a = gFindBar._findField.value; + b = gFindBar._browser.finder._fastFind.searchString; + c = gFindBar._browser.finder.searchString; + ok(a == searchStr && b == c, "testFindWithHighlight 2: " + searchStr + + ", " + a + ", " + b + ", " + c + "."); + + let highlightButton = gFindBar.getElement("highlight"); + highlightButton.click(); + ok(highlightButton.checked, "testFindWithHighlight 3: Highlight All should be checked."); + + a = gFindBar._findField.value; + b = gFindBar._browser.finder._fastFind.searchString; + c = gFindBar._browser.finder.searchString; + ok(a == searchStr && b == c, "testFindWithHighlight 4: " + a + ", " + b + ", " + c + "."); + + gFindBar.onFindAgainCommand(); + a = gFindBar._findField.value; + b = gFindBar._browser.finder._fastFind.searchString; + c = gFindBar._browser.finder.searchString; + ok(a == b && b == c, "testFindWithHighlight 5: " + a + ", " + b + ", " + c + "."); + + highlightButton.click(); + ok(!highlightButton.checked, "testFindWithHighlight: Highlight All should be unchecked."); + + // Regression test for bug 1316515. + searchStr = "e"; + gFindBar.clear(); + yield enterStringIntoFindField(searchStr); + yield promiseExpectRangeCount(0); + + highlightButton.click(); + ok(highlightButton.checked, "testFindWithHighlight: Highlight All should be checked."); + yield promiseHighlightFinished(); + yield promiseExpectRangeCount(3); + + synthesizeKey("VK_BACK_SPACE", {}); + yield promiseExpectRangeCount(0); + + // Regression test for bug 1316513. + highlightButton.click(); + ok(!highlightButton.checked, "testFindWithHighlight - 1316513: Highlight All should be unchecked."); + yield enterStringIntoFindField(searchStr); + + highlightButton.click(); + ok(highlightButton.checked, "testFindWithHighlight - 1316513: Highlight All should be checked."); + yield promiseHighlightFinished(); + yield promiseExpectRangeCount(3); + + let promise = BrowserTestUtils.browserLoaded(gBrowser); + gBrowser.reload(); + yield promise; + + ok(highlightButton.checked, "testFindWithHighlight - 1316513: Highlight All " + + "should still be checked after a reload."); + synthesizeKey("VK_RETURN", {}); + yield promiseHighlightFinished(); + yield promiseExpectRangeCount(3); + + // Uncheck at test end to not interfere with other test functions that are + // run after this one. + highlightButton.click(); + } + + function* testQuickFindText() { + yield clearFocus(); + + yield ContentTask.spawn(gBrowser, null, function* () { + let document = content.document; + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, "/".charCodeAt(0)); + document.documentElement.dispatchEvent(event); + }); + + ok(!gFindBar.hidden, "testQuickFindText: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testQuickFindText: find field is not focused"); + + yield enterStringIntoFindField(SEARCH_TEXT); + yield ContentTask.spawn(gBrowser, { SEARCH_TEXT }, function* (args) { + Assert.equal(content.getSelection().toString(), args.SEARCH_TEXT, + "testQuickFindText: failed to find '" + args.SEARCH_TEXT + "'"); + }); + testClipboardSearchString(SEARCH_TEXT); + } + + function* testFindCountUI(linksOnly = false) { + yield clearFocus(); + + if (linksOnly) { + yield ContentTask.spawn(gBrowser, null, function* () { + let document = content.document; + let event = document.createEvent("KeyboardEvent"); + event.initKeyEvent("keypress", true, true, null, false, false, + false, false, 0, "'".charCodeAt(0)); + document.documentElement.dispatchEvent(event); + }); + } else { + document.getElementById("cmd_find").doCommand(); + } + + ok(!gFindBar.hidden, "testFindCountUI: failed to open findbar"); + ok(document.commandDispatcher.focusedElement == gFindBar._findField.inputField, + "testFindCountUI: find field is not focused"); + + let promise; + let matchCase = gFindBar.getElement("find-case-sensitive"); + if (matchCase.checked) { + promise = promiseFindResult(); + matchCase.click(); + yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20)); + yield promise; + } + + let foundMatches = gFindBar._foundMatches; + let tests = [{ + text: "t", + current: linksOnly ? 1 : 5, + total: linksOnly ? 2 : 10, + }, { + text: "te", + current: linksOnly ? 1 : 3, + total: linksOnly ? 1 : 5, + }, { + text: "tes", + current: 1, + total: linksOnly ? 1 : 2, + }, { + text: "texxx", + current: 0, + total: 0 + }]; + let regex = /([\d]*)\sof\s([\d]*)/; + + function assertMatches(aTest, aMatches) { + is(aMatches[1], String(aTest.current), + `${linksOnly ? "[Links-only] " : ""}Currently highlighted match should be at ${aTest.current} for '${aTest.text}'`); + is(aMatches[2], String(aTest.total), + `${linksOnly ? "[Links-only] " : ""}Total amount of matches should be ${aTest.total} for '${aTest.text}'`); + } + + for (let test of tests) { + gFindBar._findField.select(); + gFindBar._findField.focus(); + + let timeout = ITERATOR_TIMEOUT; + if (test.text.length == 1) + timeout *= 4; + else if (test.text.length == 2) + timeout *= 2; + timeout += 20; + yield new Promise(resolve => setTimeout(resolve, timeout)); + yield enterStringIntoFindField(test.text, false); + yield promiseMatchesCountResult(); + let matches = foundMatches.value.match(regex); + if (!test.total) { + ok(!matches, "No message should be shown when 0 matches are expected"); + } else { + assertMatches(test, matches); + for (let i = 1; i < test.total; i++) { + yield new Promise(resolve => setTimeout(resolve, timeout)); + gFindBar.onFindAgainCommand(); + yield promiseMatchesCountResult(); + // test.current + 1, test.current + 2, ..., test.total, 1, ..., test.current + let current = (test.current + i - 1) % test.total + 1; + assertMatches({ + text: test.text, + current: current, + total: test.total + }, foundMatches.value.match(regex)); + } + } + } + } + + // See bug 1051187. + function* testFindAfterCaseChanged() { + // Search to set focus on "Text Test" so that searching for "t" selects first + // (upper case!) "T". + yield enterStringIntoFindField(SEARCH_TEXT); + gFindBar.clear(); + + gPrefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0); + + yield enterStringIntoFindField("t"); + yield ContentTask.spawn(gBrowser, null, function* () { + Assert.equal(content.getSelection().toString(), "T", "First T should be selected."); + }); + + gPrefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 1); + yield ContentTask.spawn(gBrowser, null, function* () { + Assert.equal(content.getSelection().toString(), "t", "First t should be selected."); + }); + } + + // Make sure that _findFailedString is cleared: + // 1. Do a search that fails with case sensitivity but matches with no case sensitivity. + // 2. Uncheck case sensitivity button to match the string. + function* testFailedStringReset() { + gPrefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 1); + + yield enterStringIntoFindField(SEARCH_TEXT.toUpperCase(), false); + yield ContentTask.spawn(gBrowser, null, function* () { + Assert.equal(content.getSelection().toString(), "", "Not found."); + }); + + gPrefsvc.setIntPref("accessibility.typeaheadfind.casesensitive", 0); + yield ContentTask.spawn(gBrowser, { SEARCH_TEXT }, function* (args) { + Assert.equal(content.getSelection().toString(), args.SEARCH_TEXT, + "Search text should be selected."); + }); + } + + function testClipboardSearchString(aExpected) { + if (!gHasFindClipboard) + return; + + if (!aExpected) + aExpected = ""; + var searchStr = gFindBar.browser.finder.clipboardSearchString; + ok(searchStr.toLowerCase() == aExpected.toLowerCase(), + "testClipboardSearchString: search string not set to '" + aExpected + + "', instead found '" + searchStr + "'"); + } + + // See bug 967982. + function* testFindAgainNotFound() { + yield openFindbar(); + yield enterStringIntoFindField(NOT_FOUND_TEXT, false); + gFindBar.close(); + ok(gFindBar.hidden, "The findbar is closed."); + let promise = promiseFindResult(); + gFindBar.onFindAgainCommand(); + yield promise; + ok(!gFindBar.hidden, "Unsuccessful Find Again opens the find bar."); + + yield enterStringIntoFindField(SEARCH_TEXT); + gFindBar.close(); + ok(gFindBar.hidden, "The findbar is closed."); + promise = promiseFindResult(); + gFindBar.onFindAgainCommand(); + yield promise; + ok(gFindBar.hidden, "Successful Find Again leaves the find bar closed."); + } + + function* testToggleEntireWord() { + yield openFindbar(); + let promise = promiseFindResult(); + yield enterStringIntoFindField("Tex", false); + let result = yield promise; + is(result.result, Ci.nsITypeAheadFind.FIND_FOUND, "Text should be found"); + + yield new Promise(resolve => setTimeout(resolve, ITERATOR_TIMEOUT + 20)); + promise = promiseFindResult(); + let check = gFindBar.getElement("find-entire-word"); + check.click(); + result = yield promise; + is(result.result, Ci.nsITypeAheadFind.FIND_NOTFOUND, "Text should NOT be found"); + + check.click(); + gFindBar.close(true); + } + ]]></script> + + <commandset> + <command id="cmd_find" oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <browser type="content-primary" flex="1" id="content-remote" remote="true" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/toolkit/content/tests/chrome/frame_popup_anchor.xul b/toolkit/content/tests/chrome/frame_popup_anchor.xul new file mode 100644 index 0000000000..be6254ce01 --- /dev/null +++ b/toolkit/content/tests/chrome/frame_popup_anchor.xul @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<menupopup id="popup" onpopupshowing="if (isSecondTest) popupShowing(event)" onpopupshown="popupShown()" + onpopuphidden="nextTest()"> + <menuitem label="One"/> + <menuitem label="Two"/> +</menupopup> + +<button id="button" label="OK" popup="popup"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var isSecondTest = false; + +function openPopup() +{ + document.getElementById("popup").openPopup(parent.document.getElementById("outerbutton"), "after_start", 3, 1); +} + +function popupShowing(event) +{ + var buttonrect = document.getElementById("button").getBoundingClientRect(); + parent.opener.wrappedJSObject.SimpleTest.is(event.clientX, buttonrect.left + 6, "popup clientX with mouse"); + parent.opener.wrappedJSObject.SimpleTest.is(event.clientY, buttonrect.top + 6, "popup clientY with mouse"); +} + +function popupShown() +{ + var left, top; + var popuprect = document.getElementById("popup").getBoundingClientRect(); + if (isSecondTest) { + var buttonrect = document.getElementById("button").getBoundingClientRect(); + left = buttonrect.left + 6; + top = buttonrect.top + 6; + } + else { + var iframerect = parent.document.getElementById("frame").getBoundingClientRect(); + var buttonrect = parent.document.getElementById("outerbutton").getBoundingClientRect(); + + // The popup should appear anchored on the bottom left edge of the button, however + // the client rectangle is relative to the iframe's document. Thus the coordinates + // are: + // left = iframe's left - anchor button's left - 3 pixel offset passed to openPopup + + // iframe border (17px) + iframe padding (0) + // top = iframe's top - anchor button's bottom - 1 pixel offset passed to openPopup + + // iframe border (0) + iframe padding (3px); + left = -(Math.round(iframerect.left) - Math.round(buttonrect.left) + 14); + top = -(Math.round(iframerect.top) - Math.round(buttonrect.bottom) + 2); + } + + var testid = isSecondTest ? "with mouse" : "anchored to parent frame"; + parent.opener.wrappedJSObject.SimpleTest.is(Math.round(popuprect.left), left, "popup left " + testid); + parent.opener.wrappedJSObject.SimpleTest.is(Math.round(popuprect.top), top, "popup top " + testid); + + document.getElementById("popup").hidePopup(); +} + +function nextTest() +{ + if (isSecondTest) { + parent.opener.wrappedJSObject.SimpleTest.finish(); + parent.close(); + } + else { + // this second test ensures that the popupshowing coordinates when a popup in + // a frame is opened are correct + isSecondTest = true; + synthesizeMouse(document.getElementById("button"), 6, 6, { }); + } +} + +]]> +</script> + +</page> diff --git a/toolkit/content/tests/chrome/frame_popupremoving_frame.xul b/toolkit/content/tests/chrome/frame_popupremoving_frame.xul new file mode 100644 index 0000000000..e8f00ce7a9 --- /dev/null +++ b/toolkit/content/tests/chrome/frame_popupremoving_frame.xul @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Removing Frame Tests" + onload="setTimeout(init, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<hbox> + +<menu id="separatemenu1" label="Menu"> + <menupopup id="separatepopup1" onpopupshown="document.getElementById('separatemenu2').open = true"> + <menuitem label="L1 One"/> + <menuitem label="L1 Two"/> + <menuitem label="L1 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu2" label="Menu"> + <menupopup id="separatepopup2" onpopupshown="document.getElementById('separatemenu3').open = true"> + <menuitem label="L2 One"/> + <menuitem label="L2 Two"/> + <menuitem label="L2 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu3" label="Menu" onpopupshown="document.getElementById('separatemenu4').open = true"> + <menupopup id="separatepopup3"> + <menuitem label="L3 One"/> + <menuitem label="L3 Two"/> + <menuitem label="L3 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu4" label="Menu" onpopupshown="document.getElementById('nestedmenu1').open = true"> + <menupopup id="separatepopup3"> + <menuitem label="L4 One"/> + <menuitem label="L4 Two"/> + <menuitem label="L4 Three"/> + </menupopup> +</menu> + +</hbox> + +<menu id="nestedmenu1" label="Menu"> + <menupopup id="nestedpopup1" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu2" label="Menu"> + <menupopup id="nestedpopup2" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu3" label="Menu"> + <menupopup id="nestedpopup3" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu4" label="Menu" onpopupshown="parent.popupsOpened()"> + <menupopup id="nestedpopup4"> + <menuitem label="Nested One"/> + <menuitem label="Nested Two"/> + <menuitem label="Nested Three"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> +</menu> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function init() +{ + document.getElementById("separatemenu1").open = true; +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/frame_subframe_origin_subframe1.xul b/toolkit/content/tests/chrome/frame_subframe_origin_subframe1.xul new file mode 100644 index 0000000000..c85083cb79 --- /dev/null +++ b/toolkit/content/tests/chrome/frame_subframe_origin_subframe1.xul @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<page id="frame1" + style="background-color:green;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<spacer height="10px"/> +<iframe + style="margin:10px; min-height:170px; max-width:200px; max-height:200px; border:solid 1px white;" + src="frame_subframe_origin_subframe2.xul"></iframe> +<spacer height="3px"/> +<caption id="cap1" style="min-width:200px; max-width:200px; background-color:white;" label=""/> +<script class="testbody" type="application/javascript"> + +// Fire a mouse move event aimed at this window, and check to be +// sure the client coords translate from widget to the dom correctly. + +function runTests() +{ + synthesizeMouse(document.getElementById("frame1"), 3, 4, { type: "mousemove" }); +} + +function mouseMove(e) { + e.stopPropagation(); + var element = e.target; + var el = document.getElementById("cap1"); + el.label = "client: (" + e.clientX + "," + e.clientY + ")"; + parent.opener.wrappedJSObject.SimpleTest.is(e.clientX, 3, "mouse event clientX on sub frame 1"); + parent.opener.wrappedJSObject.SimpleTest.is(e.clientY, 4, "mouse event clientY on sub frame 1"); + // fire the next test on the sub frame + frames[0].runTests(); +} + +window.addEventListener("mousemove", mouseMove, false); + +</script> +</page> diff --git a/toolkit/content/tests/chrome/frame_subframe_origin_subframe2.xul b/toolkit/content/tests/chrome/frame_subframe_origin_subframe2.xul new file mode 100644 index 0000000000..92ef64b898 --- /dev/null +++ b/toolkit/content/tests/chrome/frame_subframe_origin_subframe2.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<page id="frame2" + style="background-color:red;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<spacer height="10px"/> +<caption id="cap2" style="background-color:white;" label=""/> +<script class="testbody" type="application/javascript"> + +// Fire a mouse move event aimed at this window, and check to be +// sure the client coords translate from widget to the dom correctly. + +function runTests() +{ + synthesizeMouse(document.getElementById("frame2"), 6, 5, { type: "mousemove" }); +} + +function mouseMove(e) { + e.stopPropagation(); + var element = e.target; + var el = document.getElementById("cap2"); + el.label = "client: (" + e.clientX + "," + e.clientY + ")"; + parent.parent.opener.wrappedJSObject.SimpleTest.is(e.clientX, 6, "mouse event clientX on sub frame 2"); + parent.parent.opener.wrappedJSObject.SimpleTest.is(e.clientY, 5, "mouse event clientY on sub frame 2"); + parent.parent.opener.wrappedJSObject.SimpleTest.finish(); + parent.parent.close(); +} + +window.addEventListener("mousemove",mouseMove, false); + +</script> +</page> diff --git a/toolkit/content/tests/chrome/popup_childframe_node.xul b/toolkit/content/tests/chrome/popup_childframe_node.xul new file mode 100644 index 0000000000..512f5f8c26 --- /dev/null +++ b/toolkit/content/tests/chrome/popup_childframe_node.xul @@ -0,0 +1,2 @@ +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" width="80" height="80" + onclick="document.documentElement.setAttribute('data', 'x' + document.popupNode)"/> diff --git a/toolkit/content/tests/chrome/popup_trigger.js b/toolkit/content/tests/chrome/popup_trigger.js new file mode 100644 index 0000000000..920d4d0702 --- /dev/null +++ b/toolkit/content/tests/chrome/popup_trigger.js @@ -0,0 +1,859 @@ +var gMenuPopup = null; +var gTrigger = null; +var gIsMenu = false; +var gScreenX = -1, gScreenY = -1; +var gCachedEvent = null; +var gCachedEvent2 = null; + +function cacheEvent(modifiers) +{ + var cachedEvent = null; + + var mouseFn = function(event) { + cachedEvent = event; + } + + window.addEventListener("mousedown", mouseFn, false); + synthesizeMouse(document.documentElement, 0, 0, modifiers); + window.removeEventListener("mousedown", mouseFn, false); + + return cachedEvent; +} + +function runTests() +{ + if (screen.height < 768) { + ok(false, "popup tests are likely to fail for screen heights less than 768 pixels"); + } + + gMenuPopup = document.getElementById("thepopup"); + gTrigger = document.getElementById("trigger"); + + gIsMenu = gTrigger.boxObject instanceof MenuBoxObject; + + // a hacky way to get the screen position of the document. Cache the event + // so that we can use it in calls to openPopup. + gCachedEvent = cacheEvent({ shiftKey: true }); + gScreenX = gCachedEvent.screenX; + gScreenY = gCachedEvent.screenY; + gCachedEvent2 = cacheEvent({ altKey: true, ctrlKey: true, shiftKey: true, metaKey: true }); + + startPopupTests(popupTests); +} + +var popupTests = [ +{ + testname: "mouse click on trigger", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + test: function() { + // for menus, no trigger will be set. For non-menus using the popup + // attribute, the trigger will be set to the node with the popup attribute + gExpectedTriggerNode = gIsMenu ? "notset" : gTrigger; + synthesizeMouse(gTrigger, 4, 4, { }); + }, + result: function (testname) { + gExpectedTriggerNode = null; + // menus are the anchor but non-menus are opened at screen coordinates + is(gMenuPopup.anchorNode, gIsMenu ? gTrigger : null, testname + " anchorNode"); + // menus are opened internally, but non-menus have a mouse event which + // triggered them + is(gMenuPopup.triggerNode, gIsMenu ? null : gTrigger, testname + " triggerNode"); + is(document.popupNode, gIsMenu ? null : gTrigger, testname + " document.popupNode"); + is(document.tooltipNode, null, testname + " document.tooltipNode"); + // check to ensure the popup node for a different document isn't used + if (window.opener) + is(window.opener.document.popupNode, null, testname + " opener.document.popupNode"); + + // this will be used in some tests to ensure the size doesn't change + var popuprect = gMenuPopup.getBoundingClientRect(); + gPopupWidth = Math.round(popuprect.width); + gPopupHeight = Math.round(popuprect.height); + + checkActive(gMenuPopup, "", testname); + checkOpen("trigger", testname); + // if a menu, the popup should be opened underneath the menu in the + // 'after_start' position, otherwise it is opened at the mouse position + if (gIsMenu) + compareEdge(gTrigger, gMenuPopup, "after_start", 0, 0, testname); + } +}, +{ + // check that pressing cursor down while there is no selection + // highlights the first item + testname: "cursor down no selection", + events: [ "DOMMenuItemActive item1" ], + test: function() { synthesizeKey("VK_DOWN", { }); }, + result: function(testname) { checkActive(gMenuPopup, "item1", testname); } +}, +{ + // check that pressing cursor up wraps and highlights the last item + testname: "cursor up wrap", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive last" ], + test: function() { synthesizeKey("VK_UP", { }); }, + result: function(testname) { + checkActive(gMenuPopup, "last", testname); + } +}, +{ + // check that pressing cursor down wraps and highlights the first item + testname: "cursor down wrap", + events: [ "DOMMenuItemInactive last", "DOMMenuItemActive item1" ], + test: function() { synthesizeKey("VK_DOWN", { }); }, + result: function(testname) { checkActive(gMenuPopup, "item1", testname); } +}, +{ + // check that pressing cursor down highlights the second item + testname: "cursor down", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive item2" ], + test: function() { synthesizeKey("VK_DOWN", { }); }, + result: function(testname) { checkActive(gMenuPopup, "item2", testname); } +}, +{ + // check that pressing cursor up highlights the second item + testname: "cursor up", + events: [ "DOMMenuItemInactive item2", "DOMMenuItemActive item1" ], + test: function() { synthesizeKey("VK_UP", { }); }, + result: function(testname) { checkActive(gMenuPopup, "item1", testname); } +}, +{ + // cursor left should not do anything + testname: "cursor left", + test: function() { synthesizeKey("VK_LEFT", { }); }, + result: function(testname) { checkActive(gMenuPopup, "item1", testname); } +}, +{ + // cursor right should not do anything + testname: "cursor right", + test: function() { synthesizeKey("VK_RIGHT", { }); }, + result: function(testname) { checkActive(gMenuPopup, "item1", testname); } +}, +{ + // check cursor down when a disabled item exists in the menu + testname: "cursor down disabled", + events: function() { + // On Windows, disabled items are included when navigating, but on + // other platforms, disabled items are skipped over + if (navigator.platform.indexOf("Win") == 0) { + return [ "DOMMenuItemInactive item1", "DOMMenuItemActive item2" ]; + } + return [ "DOMMenuItemInactive item1", "DOMMenuItemActive amenu" ]; + }, + test: function() { + document.getElementById("item2").disabled = true; + synthesizeKey("VK_DOWN", { }); + } +}, +{ + // check cursor up when a disabled item exists in the menu + testname: "cursor up disabled", + events: function() { + if (navigator.platform.indexOf("Win") == 0) { + return [ "DOMMenuItemInactive item2", "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", "DOMMenuItemActive item2", + "DOMMenuItemInactive item2", "DOMMenuItemActive item1" ]; + } + return [ "DOMMenuItemInactive amenu", "DOMMenuItemActive item1" ]; + }, + test: function() { + if (navigator.platform.indexOf("Win") == 0) + synthesizeKey("VK_DOWN", { }); + synthesizeKey("VK_UP", { }); + if (navigator.platform.indexOf("Win") == 0) + synthesizeKey("VK_UP", { }); + } +}, +{ + testname: "mouse click outside", + events: [ "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuItemInactive item1", "DOMMenuInactive thepopup" ], + test: function() { + gMenuPopup.hidePopup(); + // XXXndeakin event simulation fires events outside of the platform specific + // widget code so the popup capturing isn't handled. Thus, the menu won't + // rollup this way. + // synthesizeMouse(gTrigger, 0, -12, { }); + }, + result: function(testname, step) { + is(gMenuPopup.anchorNode, null, testname + " anchorNode"); + is(gMenuPopup.triggerNode, null, testname + " triggerNode"); + is(document.popupNode, null, testname + " document.popupNode"); + checkClosed("trigger", testname); + } +}, +{ + // these tests check to ensure that passing an anchor and position + // puts the popup in the right place + testname: "open popup anchored", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: ["before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap", + "topleft topleft", "topcenter topleft", "topright topleft", + "leftcenter topright", "rightcenter topright", + "bottomleft bottomleft", "bottomcenter bottomleft", "bottomright bottomleft", + "topleft bottomright", "bottomcenter bottomright", "rightcenter topright"], + test: function(testname, step) { + gExpectedTriggerNode = "notset"; + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result: function(testname, step) { + // no triggerNode because it was opened without passing an event + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, gTrigger, testname + " anchorNode"); + is(gMenuPopup.triggerNode, null, testname + " triggerNode"); + is(document.popupNode, null, testname + " document.popupNode"); + compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); + } +}, +{ + // these tests check the same but with a 10 pixel margin on the popup + testname: "open popup anchored with margin", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: ["before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap", + "topleft topleft", "topcenter topleft", "topright topleft", + "leftcenter topright", "rightcenter topright", + "bottomleft bottomleft", "bottomcenter bottomleft", "bottomright bottomleft", + "topleft bottomright", "bottomcenter bottomright", "rightcenter topright"], + test: function(testname, step) { + gMenuPopup.setAttribute("style", "margin: 10px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result: function(testname, step) { + var rightmod = step == "before_end" || step == "after_end" || + step == "start_before" || step == "start_after" || + step.match(/topright$/) || step.match(/bottomright$/); + var bottommod = step == "before_start" || step == "before_end" || + step == "start_after" || step == "end_after" || + step.match(/bottomleft$/) || step.match(/bottomright$/); + compareEdge(gTrigger, gMenuPopup, step, rightmod ? -10 : 10, bottommod ? -10 : 10, testname); + gMenuPopup.removeAttribute("style"); + } +}, +{ + // these tests check the same but with a -8 pixel margin on the popup + testname: "open popup anchored with negative margin", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: ["before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"], + test: function(testname, step) { + gMenuPopup.setAttribute("style", "margin: -8px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result: function(testname, step) { + var rightmod = step == "before_end" || step == "after_end" || + step == "start_before" || step == "start_after"; + var bottommod = step == "before_start" || step == "before_end" || + step == "start_after" || step == "end_after"; + compareEdge(gTrigger, gMenuPopup, step, rightmod ? 8 : -8, bottommod ? 8 : -8, testname); + gMenuPopup.removeAttribute("style"); + } +}, + { + testname: "open popup with large positive margin", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: ["before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"], + test: function(testname, step) { + gMenuPopup.setAttribute("style", "margin: 1000px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result: function(testname, step) { + var popuprect = gMenuPopup.getBoundingClientRect(); + // as there is more room on the 'end' or 'after' side, popups will always + // appear on the right or bottom corners, depending on which side they are + // allowed to be flipped by. + var expectedleft = step == "before_end" || step == "after_end" ? + 0 : Math.round(window.innerWidth - gPopupWidth); + var expectedtop = step == "start_after" || step == "end_after" ? + 0 : Math.round(window.innerHeight - gPopupHeight); + is(Math.round(popuprect.left), expectedleft, testname + " x position " + step); + is(Math.round(popuprect.top), expectedtop, testname + " y position " + step); + gMenuPopup.removeAttribute("style"); + } +}, +{ + testname: "open popup with large negative margin", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: ["before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"], + test: function(testname, step) { + gMenuPopup.setAttribute("style", "margin: -1000px;"); + gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false); + }, + result: function(testname, step) { + var popuprect = gMenuPopup.getBoundingClientRect(); + // using negative margins causes the reverse of positive margins, and + // popups will appear on the left or top corners. + var expectedleft = step == "before_end" || step == "after_end" ? + Math.round(window.innerWidth - gPopupWidth) : 0; + var expectedtop = step == "start_after" || step == "end_after" ? + Math.round(window.innerHeight - gPopupHeight) : 0; + is(Math.round(popuprect.left), expectedleft, testname + " x position " + step); + is(Math.round(popuprect.top), expectedtop, testname + " y position " + step); + gMenuPopup.removeAttribute("style"); + } +}, +{ + testname: "popup with unknown step", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function() { + gMenuPopup.openPopup(gTrigger, "other", 0, 0, false, false); + }, + result: function (testname) { + var triggerrect = gMenuPopup.getBoundingClientRect(); + var popuprect = gMenuPopup.getBoundingClientRect(); + is(Math.round(popuprect.left), triggerrect.left, testname + " x position "); + is(Math.round(popuprect.top), triggerrect.top, testname + " y position "); + } +}, +{ + // these tests check to ensure that the position attribute can be used + // to set the position of a popup instead of passing it as an argument + testname: "open popup anchored with attribute", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: ["before_start", "before_end", "after_start", "after_end", + "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap", + "topcenter topleft", "topright bottomright", "leftcenter topright"], + test: function(testname, step) { + gMenuPopup.setAttribute("position", step); + gMenuPopup.openPopup(gTrigger, "", 0, 0, false, false); + }, + result: function(testname, step) { compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); } +}, +{ + // this test checks to ensure that the attributes override flag to openPopup + // can be used to override the popup's position. This test also passes an + // event to openPopup to check the trigger node. + testname: "open popup anchored with override", + events: [ "popupshowing thepopup 0010", "popupshown thepopup" ], + test: function(testname, step) { + // attribute overrides the position passed in + gMenuPopup.setAttribute("position", "end_after"); + gExpectedTriggerNode = gCachedEvent.target; + gMenuPopup.openPopup(gTrigger, "before_start", 0, 0, false, true, gCachedEvent); + }, + result: function(testname, step) { + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, gTrigger, testname + " anchorNode"); + is(gMenuPopup.triggerNode, gCachedEvent.target, testname + " triggerNode"); + is(document.popupNode, gCachedEvent.target, testname + " document.popupNode"); + compareEdge(gTrigger, gMenuPopup, "end_after", 0, 0, testname); + } +}, +{ + testname: "close popup with escape", + events: [ "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuInactive thepopup", ], + test: function(testname, step) { + synthesizeKey("VK_ESCAPE", { }); + checkClosed("trigger", testname); + } +}, +{ + // check that offsets may be supplied to the openPopup method + testname: "open popup anchored with offsets", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + // attribute is empty so does not override + gMenuPopup.setAttribute("position", ""); + gMenuPopup.openPopup(gTrigger, "before_start", 5, 10, true, true); + }, + result: function(testname, step) { compareEdge(gTrigger, gMenuPopup, "before_start", 5, 10, testname); } +}, +{ + // these tests check to ensure that passing an anchor and position + // puts the popup in the right place + testname: "show popup anchored", + condition: function() { + // only perform this test for popups not in a menu, such as those using + // the popup attribute, as the showPopup implementation in popup.xml + // calls openMenu if the popup is inside a menu + return !gIsMenu; + }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + steps: [["topleft", "topleft"], + ["topleft", "topright"], ["topleft", "bottomleft"], + ["topright", "topleft"], ["topright", "bottomright"], + ["bottomleft", "bottomright"], ["bottomleft", "topleft"], + ["bottomright", "bottomleft"], ["bottomright", "topright"]], + test: function(testname, step) { + // the attributes should be ignored + gMenuPopup.setAttribute("popupanchor", "topright"); + gMenuPopup.setAttribute("popupalign", "bottomright"); + gMenuPopup.setAttribute("position", "end_after"); + gMenuPopup.showPopup(gTrigger, -1, -1, "popup", step[0], step[1]); + }, + result: function(testname, step) { + var pos = convertPosition(step[0], step[1]); + compareEdge(gTrigger, gMenuPopup, pos, 0, 0, testname); + gMenuPopup.removeAttribute("popupanchor"); + gMenuPopup.removeAttribute("popupalign"); + gMenuPopup.removeAttribute("position"); + } +}, +{ + testname: "show popup with position", + condition: function() { return !gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + gMenuPopup.showPopup(gTrigger, gScreenX + 60, gScreenY + 15, + "context", "topleft", "bottomright"); + }, + result: function(testname, step) { + var rect = gMenuPopup.getBoundingClientRect(); + ok(true, gScreenX + "," + gScreenY); + is(rect.left, 60, testname + " left"); + is(rect.top, 15, testname + " top"); + ok(rect.right, testname + " right is " + rect.right); + ok(rect.bottom, testname + " bottom is " + rect.bottom); + } +}, +{ + // if no anchor is supplied to openPopup, it should be opened relative + // to the viewport. + testname: "open popup unanchored", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + test: function(testname, step) { gMenuPopup.openPopup(null, "after_start", 6, 8, false); }, + result: function(testname, step) { + var rect = gMenuPopup.getBoundingClientRect(); + ok(rect.left == 6 && rect.top == 8 && rect.right && rect.bottom, testname); + } +}, +{ + testname: "activate menuitem with mouse", + events: [ "DOMMenuInactive thepopup", "command item3", + "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuItemInactive item3" ], + test: function(testname, step) { + var item3 = document.getElementById("item3"); + synthesizeMouse(item3, 4, 4, { }); + }, + result: function(testname, step) { checkClosed("trigger", testname); } +}, +{ + testname: "close popup", + condition: function() { return false; }, + events: [ "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuInactive thepopup" ], + test: function(testname, step) { gMenuPopup.hidePopup(); } +}, +{ + testname: "open popup at screen", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + test: function(testname, step) { + gExpectedTriggerNode = "notset"; + gMenuPopup.openPopupAtScreen(gScreenX + 24, gScreenY + 20, false); + }, + result: function(testname, step) { + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, null, testname + " anchorNode"); + is(gMenuPopup.triggerNode, null, testname + " triggerNode"); + is(document.popupNode, null, testname + " document.popupNode"); + var rect = gMenuPopup.getBoundingClientRect(); + is(rect.left, 24, testname + " left"); + is(rect.top, 20, testname + " top"); + ok(rect.right, testname + " right is " + rect.right); + ok(rect.bottom, testname + " bottom is " + rect.bottom); + } +}, +{ + // check that pressing a menuitem's accelerator selects it. Note that + // the menuitem with the M accesskey overrides the earlier menuitem that + // begins with M. + testname: "menuitem accelerator", + events: [ "DOMMenuItemActive amenu", "DOMMenuItemInactive amenu", + "DOMMenuInactive thepopup", + "command amenu", "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuItemInactive amenu" + ], + test: function() { synthesizeKey("M", { }); }, + result: function(testname) { checkClosed("trigger", testname); } +}, +{ + testname: "open context popup at screen", + events: [ "popupshowing thepopup 0010", "popupshown thepopup" ], + test: function(testname, step) { + gExpectedTriggerNode = gCachedEvent.target; + gMenuPopup.openPopupAtScreen(gScreenX + 8, gScreenY + 16, true, gCachedEvent); + }, + result: function(testname, step) { + gExpectedTriggerNode = null; + is(gMenuPopup.anchorNode, null, testname + " anchorNode"); + is(gMenuPopup.triggerNode, gCachedEvent.target, testname + " triggerNode"); + is(document.popupNode, gCachedEvent.target, testname + " document.popupNode"); + + var childframe = document.getElementById("childframe"); + if (childframe) { + for (var t = 0; t < 2; t++) { + var child = childframe.contentDocument; + var evt = child.createEvent("Event"); + evt.initEvent("click", true, true); + child.documentElement.dispatchEvent(evt); + is(child.documentElement.getAttribute("data"), "xnull", + "cannot get popupNode from other document"); + child.documentElement.setAttribute("data", "none"); + // now try again with document.popupNode set explicitly + document.popupNode = gCachedEvent.target; + } + } + + var openX = 8; + var openY = 16; + var rect = gMenuPopup.getBoundingClientRect(); + is(rect.left, openX + (platformIsMac() ? 1 : 2), testname + " left"); + is(rect.top, openY + (platformIsMac() ? -6 : 2), testname + " top"); + ok(rect.right, testname + " right is " + rect.right); + ok(rect.bottom, testname + " bottom is " + rect.bottom); + } +}, +{ + // pressing a letter that doesn't correspond to an accelerator, but does + // correspond to the first letter in a menu's label. The menu should not + // close because there is more than one item corresponding to that letter + testname: "menuitem with non accelerator", + events: [ "DOMMenuItemActive one" ], + test: function() { synthesizeKey("O", { }); }, + result: function(testname) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "one", testname); + } +}, +{ + // pressing the letter again should select the next one that starts with + // that letter + testname: "menuitem with non accelerator again", + events: [ "DOMMenuItemInactive one", "DOMMenuItemActive submenu" ], + test: function() { synthesizeKey("O", { }); }, + result: function(testname) { + // 'submenu' is a menu but it should not be open + checkOpen("trigger", testname); + checkClosed("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + } +}, +{ + // open the submenu with the cursor right key + testname: "open submenu with cursor right", + events: [ "popupshowing submenupopup", "DOMMenuItemActive submenuitem", + "popupshown submenupopup" ], + test: function() { synthesizeKey("VK_RIGHT", { }); }, + result: function(testname) { + checkOpen("trigger", testname); + checkOpen("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive(document.getElementById("submenupopup"), "submenuitem", testname); + } +}, +{ + // close the submenu with the cursor left key + testname: "close submenu with cursor left", + events: [ "popuphiding submenupopup", "popuphidden submenupopup", + "DOMMenuItemInactive submenuitem", "DOMMenuInactive submenupopup", + "DOMMenuItemActive submenu" ], + test: function() { synthesizeKey("VK_LEFT", { }); }, + result: function(testname) { + checkOpen("trigger", testname); + checkClosed("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive(document.getElementById("submenupopup"), "", testname); + } +}, +{ + // open the submenu with the enter key + testname: "open submenu with enter", + events: [ "popupshowing submenupopup", "DOMMenuItemActive submenuitem", + "popupshown submenupopup" ], + test: function() { synthesizeKey("VK_RETURN", { }); }, + result: function(testname) { + checkOpen("trigger", testname); + checkOpen("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive(document.getElementById("submenupopup"), "submenuitem", testname); + } +}, +{ + // close the submenu with the escape key + testname: "close submenu with escape", + events: [ "popuphiding submenupopup", "popuphidden submenupopup", + "DOMMenuItemInactive submenuitem", "DOMMenuInactive submenupopup", + "DOMMenuItemActive submenu" ], + test: function() { synthesizeKey("VK_ESCAPE", { }); }, + result: function(testname) { + checkOpen("trigger", testname); + checkClosed("submenu", testname); + checkActive(gMenuPopup, "submenu", testname); + checkActive(document.getElementById("submenupopup"), "", testname); + } +}, +{ + // pressing the letter again when the next item is disabled should still + // select the disabled item on Windows, but select the next item on other + // platforms + testname: "menuitem with non accelerator disabled", + events: function() { + if (navigator.platform.indexOf("Win") == 0) { + return [ "DOMMenuItemInactive submenu", "DOMMenuItemActive other", + "DOMMenuItemInactive other", "DOMMenuItemActive item1" ]; + } + return [ "DOMMenuItemInactive submenu", "DOMMenuItemActive last", + "DOMMenuItemInactive last", "DOMMenuItemActive item1" ]; + }, + test: function() { synthesizeKey("O", { }); synthesizeKey("F", { }); }, + result: function(testname) { + checkActive(gMenuPopup, "item1", testname); + } +}, +{ + // pressing a letter that doesn't correspond to an accelerator nor the + // first letter of a menu. This should have no effect. + testname: "menuitem with keypress no accelerator found", + test: function() { synthesizeKey("G", { }); }, + result: function(testname) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "item1", testname); + } +}, +{ + // when only one menuitem starting with that letter exists, it should be + // selected and the menu closed + testname: "menuitem with non accelerator single", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", "DOMMenuInactive thepopup", + "command amenu", "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuItemInactive amenu", + ], + test: function() { synthesizeKey("M", { }); }, + result: function(testname) { + checkClosed("trigger", testname); + checkActive(gMenuPopup, "", testname); + } +}, +{ + testname: "open context popup at screen with all modifiers set", + events: [ "popupshowing thepopup 1111", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + gMenuPopup.openPopupAtScreen(gScreenX + 8, gScreenY + 16, true, gCachedEvent2); + } +}, +{ + testname: "open popup with open property", + events: [ "popupshowing thepopup", "popupshown thepopup" ], + test: function(testname, step) { openMenu(gTrigger); }, + result: function(testname, step) { + checkOpen("trigger", testname); + if (gIsMenu) + compareEdge(gTrigger, gMenuPopup, "after_start", 0, 0, testname); + } +}, +{ + testname: "open submenu with open property", + events: [ "popupshowing submenupopup", "DOMMenuItemActive submenu", + "popupshown submenupopup" ], + test: function(testname, step) { openMenu(document.getElementById("submenu")); }, + result: function(testname, step) { + checkOpen("trigger", testname); + checkOpen("submenu", testname); + // XXXndeakin + // getBoundingClientRect doesn't seem to working right for submenus + // so disable this test for now + // compareEdge(document.getElementById("submenu"), + // document.getElementById("submenupopup"), "end_before", 0, 0, testname); + } +}, +{ + testname: "hidePopup hides entire chain", + events: [ "popuphiding submenupopup", "popuphidden submenupopup", + "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuInactive submenupopup", + "DOMMenuItemInactive submenu", "DOMMenuItemInactive submenu", + "DOMMenuInactive thepopup", ], + test: function() { gMenuPopup.hidePopup(); }, + result: function(testname, step) { + checkClosed("trigger", testname); + checkClosed("submenu", testname); + } +}, +{ + testname: "open submenu with open property without parent open", + test: function(testname, step) { openMenu(document.getElementById("submenu")); }, + result: function(testname, step) { + checkClosed("trigger", testname); + checkClosed("submenu", testname); + } +}, +{ + testname: "open popup with open property and position", + condition: function() { return gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + test: function(testname, step) { + gMenuPopup.setAttribute("position", "before_start"); + openMenu(gTrigger); + }, + result: function(testname, step) { + compareEdge(gTrigger, gMenuPopup, "before_start", 0, 0, testname); + } +}, +{ + testname: "close popup with open property", + condition: function() { return gIsMenu; }, + events: [ "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuInactive thepopup" ], + test: function(testname, step) { closeMenu(gTrigger, gMenuPopup); }, + result: function(testname, step) { checkClosed("trigger", testname); } +}, +{ + testname: "open popup with open property, position, anchor and alignment", + condition: function() { return gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + gMenuPopup.setAttribute("position", "start_after"); + gMenuPopup.setAttribute("popupanchor", "topright"); + gMenuPopup.setAttribute("popupalign", "bottomright"); + openMenu(gTrigger); + }, + result: function(testname, step) { + compareEdge(gTrigger, gMenuPopup, "start_after", 0, 0, testname); + } +}, +{ + testname: "open popup with open property, anchor and alignment", + condition: function() { return gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + gMenuPopup.removeAttribute("position"); + gMenuPopup.setAttribute("popupanchor", "bottomright"); + gMenuPopup.setAttribute("popupalign", "topright"); + openMenu(gTrigger); + }, + result: function(testname, step) { + compareEdge(gTrigger, gMenuPopup, "after_end", 0, 0, testname); + gMenuPopup.removeAttribute("popupanchor"); + gMenuPopup.removeAttribute("popupalign"); + } +}, +{ + testname: "focus and cursor down on trigger", + condition: function() { return gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + gTrigger.focus(); + synthesizeKey("VK_DOWN", { altKey: !platformIsMac() }); + }, + result: function(testname, step) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "", testname); + } +}, +{ + testname: "focus and cursor up on trigger", + condition: function() { return gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + test: function(testname, step) { + gTrigger.focus(); + synthesizeKey("VK_UP", { altKey: !platformIsMac() }); + }, + result: function(testname, step) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "", testname); + } +}, +{ + testname: "select and enter on menuitem", + condition: function() { return gIsMenu; }, + events: [ "DOMMenuItemActive item1", "DOMMenuItemInactive item1", + "DOMMenuInactive thepopup", "command item1", + "popuphiding thepopup", "popuphidden thepopup", + "DOMMenuItemInactive item1" ], + test: function(testname, step) { + synthesizeKey("VK_DOWN", { }); + synthesizeKey("VK_RETURN", { }); + }, + result: function(testname, step) { checkClosed("trigger", testname); } +}, +{ + testname: "focus trigger and key to open", + condition: function() { return gIsMenu; }, + events: [ "popupshowing thepopup", "popupshown thepopup" ], + autohide: "thepopup", + test: function(testname, step) { + gTrigger.focus(); + synthesizeKey(platformIsMac() ? " " : "VK_F4", { }); + }, + result: function(testname, step) { + checkOpen("trigger", testname); + checkActive(gMenuPopup, "", testname); + } +}, +{ + // the menu should only open when the meta or alt key is not pressed + testname: "focus trigger and key wrong modifier", + condition: function() { return gIsMenu; }, + test: function(testname, step) { + gTrigger.focus(); + if (platformIsMac()) + synthesizeKey("VK_F4", { altKey: true }); + else + synthesizeKey("", { metaKey: true }); + }, + result: function(testname, step) { + checkClosed("trigger", testname); + } +}, +{ + testname: "mouse click on disabled menu", + condition: function() { return gIsMenu; }, + test: function(testname, step) { + gTrigger.setAttribute("disabled", "true"); + synthesizeMouse(gTrigger, 4, 4, { }); + }, + result: function(testname, step) { + checkClosed("trigger", testname); + gTrigger.removeAttribute("disabled"); + } +}, +{ + // openPopup should open the menu synchronously, however popupshown + // is fired asynchronously + testname: "openPopup synchronous", + events: [ "popupshowing thepopup", "popupshowing submenupopup", + "popupshown thepopup", "DOMMenuItemActive submenu", + "popupshown submenupopup" ], + test: function(testname, step) { + gMenuPopup.openPopup(gTrigger, "after_start", 0, 0, false, true); + document.getElementById("submenupopup"). + openPopup(gTrigger, "end_before", 0, 0, false, true); + checkOpen("trigger", testname); + checkOpen("submenu", testname); + } +}, +{ + // remove the content nodes for the popup + testname: "remove content", + test: function(testname, step) { + var submenupopup = document.getElementById("submenupopup"); + submenupopup.parentNode.removeChild(submenupopup); + var popup = document.getElementById("thepopup"); + popup.parentNode.removeChild(popup); + } +} + +]; + +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} diff --git a/toolkit/content/tests/chrome/rtlchrome/rtl.css b/toolkit/content/tests/chrome/rtlchrome/rtl.css new file mode 100644 index 0000000000..0fea010019 --- /dev/null +++ b/toolkit/content/tests/chrome/rtlchrome/rtl.css @@ -0,0 +1,2 @@ +/* Imitate RTL UI */
+window { direction: rtl; }
diff --git a/toolkit/content/tests/chrome/rtlchrome/rtl.dtd b/toolkit/content/tests/chrome/rtlchrome/rtl.dtd new file mode 100644 index 0000000000..8b32de6746 --- /dev/null +++ b/toolkit/content/tests/chrome/rtlchrome/rtl.dtd @@ -0,0 +1 @@ +<!ENTITY locale.dir "rtl"> diff --git a/toolkit/content/tests/chrome/rtlchrome/rtl.manifest b/toolkit/content/tests/chrome/rtlchrome/rtl.manifest new file mode 100644 index 0000000000..a4cc6929be --- /dev/null +++ b/toolkit/content/tests/chrome/rtlchrome/rtl.manifest @@ -0,0 +1,5 @@ +content rtlchrome /
+
+# Override intl.css with our own CSS file
+override chrome://global/locale/intl.css chrome://rtlchrome/rtl.css
+override chrome://global/locale/global.dtd chrome://rtlchrome/rtl.dtd
diff --git a/toolkit/content/tests/chrome/rtltest/content/dirtest.xul b/toolkit/content/tests/chrome/rtltest/content/dirtest.xul new file mode 100644 index 0000000000..b75d41eaa3 --- /dev/null +++ b/toolkit/content/tests/chrome/rtltest/content/dirtest.xul @@ -0,0 +1,25 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<html:style> +hbox, vbox { background-color: white; } +hbox:-moz-locale-dir(ltr) { background-color: yellow; } +vbox:-moz-locale-dir(rtl) { background-color: green; } +</html:style> + +<hbox id="hbox"> + <button label="One"/> + <button label="Two"/> + <button label="Three"/> +</hbox> +<vbox id="vbox"> + <button label="One"/> + <button label="Two"/> + <button label="Three"/> +</vbox> + +</window> diff --git a/toolkit/content/tests/chrome/rtltest/righttoleft.manifest b/toolkit/content/tests/chrome/rtltest/righttoleft.manifest new file mode 100644 index 0000000000..db98656bce --- /dev/null +++ b/toolkit/content/tests/chrome/rtltest/righttoleft.manifest @@ -0,0 +1,3 @@ +content ltrtest content/ +content rtltest content/ +locale rtltest ar-QA content/ diff --git a/toolkit/content/tests/chrome/sample_entireword_latin1.html b/toolkit/content/tests/chrome/sample_entireword_latin1.html new file mode 100644 index 0000000000..b2d66fa3c4 --- /dev/null +++ b/toolkit/content/tests/chrome/sample_entireword_latin1.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <head><title>Latin entire-word find test page</title></head> + <body> + <!-- Feel free to extend the contents of this page with more comprehensive + - Latin punctuation and/ or word markers. + --> + <p>The twins of Mammon quarrelled. Their warring plunged the world into a new darkness, and the beast abhorred the darkness. So it began to move swiftly, and grew more powerful, and went forth and multiplied. And the beasts brought fire and light to the darkness.</p> + <p>from The Book of Mozilla, 15:1</p> + </body> +</html> diff --git a/toolkit/content/tests/chrome/test_about_networking.html b/toolkit/content/tests/chrome/test_about_networking.html new file mode 100644 index 0000000000..6ffaf2ba79 --- /dev/null +++ b/toolkit/content/tests/chrome/test_about_networking.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=912103 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug </title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + const Cc = Components.classes; + const Ci = Components.interfaces; + + var dashboard = Cc['@mozilla.org/network/dashboard;1'] + .getService(Ci.nsIDashboard); + dashboard.enableLogging = true; + + var wsURI = "ws://mochi.test:8888/chrome/toolkit/content/tests/chrome/file_about_networking"; + var websocket = new WebSocket(wsURI); + + websocket.addEventListener("open", function() { + dashboard.requestWebsocketConnections(function(data) { + var found = false; + for (var i = 0; i < data.websockets.length; i++) { + if (data.websockets[i].hostport == "mochi.test:8888") { + found = true; + break; + } + } + isnot(found, false, "tested websocket entry not found"); + websocket.close(); + SimpleTest.finish(); + }); + }); + } + + window.addEventListener("DOMContentLoaded", function run() { + window.removeEventListener("DOMContentLoaded", run); + runTest(); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912103">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_arrowpanel.xul b/toolkit/content/tests/chrome/test_arrowpanel.xul new file mode 100644 index 0000000000..671c33a15c --- /dev/null +++ b/toolkit/content/tests/chrome/test_arrowpanel.xul @@ -0,0 +1,327 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Arrow Panels" + style="padding: 10px;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<stack flex="1"> + <label id="topleft" value="Top Left Corner" left="15" top="15"/> + <label id="topright" value="Top Right" right="15" top="15"/> + <label id="bottomleft" value="Bottom Left Corner" left="15" bottom="15"/> + <label id="bottomright" value="Bottom Right" right="15" bottom="15"/> + <!-- Our SimpleTest/TestRunner.js runs tests inside an iframe which sizes are W=500 H=300. + 'left' and 'top' values need to be set so that the panel (popup) has enough room to display on its 4 sides. --> + <label id="middle" value="+/- Centered" left="225" top="135"/> + <iframe id="frame" type="content" + src="data:text/html,<input id='input'>" width="100" height="100" left="225" top="120"/> +</stack> + +<panel id="panel" type="arrow" animate="false" + onpopupshown="checkPanelPosition(this)" onpopuphidden="runNextTest.next()"> + <box width="115" height="65"/> +</panel> + +<panel id="bigpanel" type="arrow" animate="false" + onpopupshown="checkBigPanel(this)" onpopuphidden="runNextTest.next()"> + <box width="125" height="3000"/> +</panel> + +<panel id="animatepanel" type="arrow" + onpopupshown="animatedPopupShown = true;" + onpopuphidden="animatedPopupHidden = true; runNextTest.next();"> + <label value="Animate Closed" height="40"/> +</panel> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +const isOSXYosemite = navigator.userAgent.indexOf("Mac OS X 10.10") != -1; + +var expectedAnchor = null; +var expectedSide = "", expectedAnchorEdge = "", expectedPack = "", expectedAlignment = ""; +var zoomFactor = 1; +var animatedPopupShown = false; +var animatedPopupHidden = false; +var runNextTest; + +function startTest() +{ + runNextTest = nextTest(); + runNextTest.next(); +} + +function nextTest() +{ + var panel = $("panel"); + + function openPopup(position, anchor, expected, anchorEdge, pack, alignment) + { + expectedAnchor = anchor instanceof Node ? anchor : $(anchor); + expectedSide = expected; + expectedAnchorEdge = anchorEdge; + expectedPack = pack; + expectedAlignment = alignment == undefined ? position : alignment; + + panel.removeAttribute("side"); + panel.openPopup(expectedAnchor, position, 0, 0, false, false, null); + } + + for (var iter = 0; iter < 2; iter++) { + openPopup("after_start", "topleft", "top", "left", "start"); + yield; + openPopup("after_start", "bottomleft", "bottom", "left", "start", "before_start"); + yield; + openPopup("before_start", "topleft", "top", "left", "start", "after_start"); + yield; + openPopup("before_start", "bottomleft", "bottom", "left", "start"); + yield; + openPopup("after_start", "middle", "top", "left", "start"); + yield; + openPopup("before_start", "middle", "bottom", "left", "start"); + yield; + + openPopup("after_start", "topright", "top", "right", "end", "after_end"); + yield; + openPopup("after_start", "bottomright", "bottom", "right", "end", "before_end"); + yield; + openPopup("before_start", "topright", "top", "right", "end", "after_end"); + yield; + openPopup("before_start", "bottomright", "bottom", "right", "end", "before_end"); + yield; + + openPopup("after_end", "middle", "top", "right", "end"); + yield; + openPopup("before_end", "middle", "bottom", "right", "end"); + yield; + + openPopup("start_before", "topleft", "left", "top", "start", "end_before"); + yield; + openPopup("start_before", "topright", "right", "top", "start"); + yield; + openPopup("end_before", "topleft", "left", "top", "start"); + yield; + openPopup("end_before", "topright", "right", "top", "start", "start_before"); + yield; + openPopup("start_before", "middle", "right", "top", "start"); + yield; + openPopup("end_before", "middle", "left", "top", "start"); + yield; + + openPopup("start_before", "bottomleft", "left", "bottom", "end", "end_after"); + yield; + openPopup("start_before", "bottomright", "right", "bottom", "end", "start_after"); + yield; + openPopup("end_before", "bottomleft", "left", "bottom", "end", "end_after"); + yield; + openPopup("end_before", "bottomright", "right", "bottom", "end", "start_after"); + yield; + + openPopup("start_after", "middle", "right", "bottom", "end"); + yield; + openPopup("end_after", "middle", "left", "bottom", "end"); + yield; + + openPopup("topcenter bottomleft", "bottomleft", "bottom", "center left", "start", "before_start"); + yield; + openPopup("bottomcenter topleft", "topleft", "top", "center left", "start", "after_start"); + yield; + openPopup("topcenter bottomright", "bottomright", "bottom", "center right", "end", "before_end"); + yield; + openPopup("bottomcenter topright", "topright", "top", "center right", "end", "after_end"); + yield; + openPopup("topcenter bottomleft", "middle", "bottom", "center left", "start", "before_start"); + yield; + openPopup("bottomcenter topleft", "middle", "top", "center left", "start", "after_start"); + yield; + + openPopup("leftcenter topright", "middle", "right", "center top", "start", "start_before"); + yield; + openPopup("rightcenter bottomleft", "middle", "left", "center bottom", "end", "end_after"); + yield; + +/* + XXXndeakin disable these parts of the test which often cause problems, see bug 626563 + + openPopup("after_start", frames[0].document.getElementById("input"), "top", "left", "start"); + yield; + + setScale(frames[0], 1.5); + openPopup("after_start", frames[0].document.getElementById("input"), "top", "left", "start"); + yield; + + setScale(frames[0], 2.5); + openPopup("before_start", frames[0].document.getElementById("input"), "bottom", "left", "start"); + yield; + + setScale(frames[0], 1); +*/ + + $("bigpanel").openPopup($("topleft"), "after_start", 0, 0, false, false, null, "start"); + yield; + + // switch to rtl mode + document.documentElement.style.direction = "rtl"; + $("topleft").setAttribute("right", "15"); + $("topright").setAttribute("left", "15"); + $("bottomleft").setAttribute("right", "15"); + $("bottomright").setAttribute("left", "15"); + $("topleft").removeAttribute("left"); + $("topright").removeAttribute("right"); + $("bottomleft").removeAttribute("left"); + $("bottomright").removeAttribute("right"); + } + + // Test that a transition occurs when opening or closing the popup. The transition is + // disabled on Linux. + if (navigator.platform.indexOf("Linux") == -1) { + function transitionEnded(event) { + if ($("animatepanel").state != "open") { + is($("animatepanel").state, "showing", "state is showing during transitionend"); + ok(!animatedPopupShown, "popupshown not fired yet") + } else { + is($("animatepanel").state, "open", "state is open after transitionend"); + ok(animatedPopupShown, "popupshown now fired") + SimpleTest.executeSoon(() => runNextTest.next()); + } + } + + // Check that the transition occurs for an arrow panel with animate="true" + window.addEventListener("transitionend", transitionEnded, false); + $("animatepanel").openPopup($("topleft"), "after_start", 0, 0, false, false, null, "start"); + is($("animatepanel").state, "showing", "state is showing"); + yield; + window.removeEventListener("transitionend", transitionEnded, false); + + synthesizeKey("VK_ESCAPE", { }); + ok(!animatedPopupHidden, "animated popup not hidden yet"); + yield; + } + + SimpleTest.finish() + yield; +} + +function setScale(win, scale) +{ + var wn = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation); + var shell = wn.QueryInterface(Components.interfaces.nsIDocShell); + var docViewer = shell.contentViewer; + docViewer.fullZoom = scale; + zoomFactor = scale; +} + +function checkPanelPosition(panel) +{ + let anchor = panel.anchorNode; + let adj = 0, hwinpos = 0, vwinpos = 0; + if (anchor.ownerDocument != document) { + var framerect = anchor.ownerDocument.defaultView.frameElement.getBoundingClientRect(); + hwinpos = framerect.left; + vwinpos = framerect.top; + } + + // Positions are reversed in rtl yet the coordinates used in the computations + // are not, so flip the expected label side and anchor edge. + var isRTL = (window.getComputedStyle(panel).direction == "rtl"); + if (isRTL) { + var flipLeftRight = val => val == "left" ? "right" : "left"; + expectedAnchorEdge = expectedAnchorEdge.replace(/(left|right)/, flipLeftRight); + expectedSide = expectedSide.replace(/(left|right)/, flipLeftRight); + } + + var panelRect = panel.getBoundingClientRect(); + var anchorRect = anchor.getBoundingClientRect(); + var contentBO = panel.firstChild.boxObject; + var contentRect = { top: contentBO.y, + left: contentBO.x, + bottom: contentBO.y + contentBO.height, + right: contentBO.x + contentBO.width }; + switch (expectedSide) { + case "top": + ok(contentRect.top > vwinpos + anchorRect.bottom * zoomFactor + 5, "panel content is below"); + break; + case "bottom": + ok(contentRect.bottom < vwinpos + anchorRect.top * zoomFactor - 5, "panel content is above"); + break; + case "left": + ok(contentRect.left > hwinpos + anchorRect.right * zoomFactor + 5, "panel content is right"); + break; + case "right": + ok(contentRect.right < hwinpos + anchorRect.left * zoomFactor - 5, "panel content is left"); + break; + } + + let iscentered = false; + if (expectedAnchorEdge.indexOf("center ") == 0) { + expectedAnchorEdge = expectedAnchorEdge.substring(7); + iscentered = true; + } + + switch (expectedAnchorEdge) { + case "top": + adj = vwinpos + parseInt(getComputedStyle(panel, "").marginTop); + if (iscentered) + adj += Math.round(anchorRect.height) / 2; + isWithinHalfPixel(panelRect.top, anchorRect.top * zoomFactor + adj, "anchored on top"); + break; + case "bottom": + adj = vwinpos + parseInt(getComputedStyle(panel, "").marginBottom); + if (iscentered) + adj += Math.round(anchorRect.height) / 2; + isWithinHalfPixel(panelRect.bottom, anchorRect.bottom * zoomFactor - adj, "anchored on bottom"); + break; + case "left": + adj = hwinpos + parseInt(getComputedStyle(panel, "").marginLeft); + if (iscentered) + adj += Math.round(anchorRect.width) / 2; + isWithinHalfPixel(panelRect.left, anchorRect.left * zoomFactor + adj, "anchored on left "); + break; + case "right": + adj = hwinpos + parseInt(getComputedStyle(panel, "").marginRight); + if (iscentered) + adj += Math.round(anchorRect.width) / 2; + if (!isOSXYosemite) + isWithinHalfPixel(panelRect.right, anchorRect.right * zoomFactor - adj, "anchored on right"); + break; + } + + is(anchor, expectedAnchor, "anchor"); + + var arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow"); + is(arrow.getAttribute("side"), expectedSide, "panel arrow side"); + is(arrow.hidden, false, "panel hidden"); + is(arrow.parentNode.pack, expectedPack, "panel arrow pack"); + is(panel.alignmentPosition, expectedAlignment, "panel alignmentPosition"); + + panel.hidePopup(); +} + +function isWithinHalfPixel(a, b, desc) +{ + ok(Math.abs(a - b) <= 0.5, desc); +} + +function checkBigPanel(panel) +{ + ok(panel.firstChild.getBoundingClientRect().height < 2800, "big panel height"); + panel.hidePopup(); +} + +SimpleTest.waitForFocus(startTest); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"/> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete2.xul b/toolkit/content/tests/chrome/test_autocomplete2.xul new file mode 100644 index 0000000000..875cddd07a --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete2.xul @@ -0,0 +1,197 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 2" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<textbox id="autocomplete" type="autocomplete" + autocompletesearch="simple" + onsearchcomplete="checkResult();"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +// Set to indicate whether or not we want autoCompleteSimple to return a result +var returnResult = false; + +const ACR = Components.interfaces.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (returnResult) { + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "SUCCESS"; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return this._param; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function() { return this.getValueAt(); }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + startSearch: function(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var componentManager = Components.manager + .QueryInterface(Components.interfaces.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + + +// Test Bug 441530 - correctly setting "nomatch" +// Test Bug 441526 - correctly setting style with "highlightnonmatches" + +SimpleTest.waitForExplicitFinish(); +setTimeout(startTest, 0); + +function startTest() { + var autocomplete = $("autocomplete"); + + // Ensure highlightNonMatches can be set correctly. + + // This should not be set by default. + is(autocomplete.hasAttribute("highlightnonmatches"), false, + "highlight nonmatches not set by default"); + + autocomplete.highlightNonMatches = "true"; + + is(autocomplete.getAttribute("highlightnonmatches"), "true", + "highlight non matches attribute set correctly"); + is(autocomplete.highlightNonMatches, true, + "highlight non matches getter returned correctly"); + + autocomplete.highlightNonMatches = "false"; + + is(autocomplete.getAttribute("highlightnonmatches"), "false", + "highlight non matches attribute set to false correctly"); + is(autocomplete.highlightNonMatches, false, + "highlight non matches getter returned false correctly"); + + ok(!autocomplete.popup.hasAttribute("autocompleteinput"), + "autocompleteinput on popup not set by default"); + + check(); +} + +function check() { + var autocomplete = $("autocomplete"); + + // Toggle this value, so we can re-use the one function. + returnResult = !returnResult; + + // blur the field to ensure that the popup is closed and that the previous + // search has stopped, then start a new search. + autocomplete.blur(); + autocomplete.focus(); + synthesizeKey("r", {}); +} + +function checkResult() { + var autocomplete = $("autocomplete"); + var style = window.getComputedStyle(autocomplete, ""); + + if (returnResult) { + // Result was returned, so there should not be a nomatch attribute + is(autocomplete.hasAttribute("nomatch"), false, + "nomatch attribute shouldn't be present here"); + + // Ensure that the style is set correctly whichever way highlightNonMatches + // is set. + autocomplete.highlightNonMatches = "true"; + + isnot(style.getPropertyCSSValue("color").cssText, "rgb(255, 0, 0)", + "not nomatch and highlightNonMatches - should not be red"); + + autocomplete.highlightNonMatches = "false"; + + isnot(style.getPropertyCSSValue("color").cssText, "rgb(255, 0, 0)", + "not nomatch and not highlightNonMatches - should not be red"); + + is (autocomplete.popup.getAttribute("autocompleteinput"), "autocomplete", + "The popup's autocompleteinput attribute is set to the ID of the textbox"); + + setTimeout(check, 0); + } + else { + // No result was returned, so there should be nomatch attribute + is(autocomplete.getAttribute("nomatch"), "true", + "nomatch attribute not correctly set when expected"); + + // Ensure that the style is set correctly whichever way highlightNonMatches + // is set. + autocomplete.highlightNonMatches = "true"; + + is(style.getPropertyCSSValue("color").cssText, "rgb(255, 0, 0)", + "nomatch and highlightNonMatches - should be red"); + + autocomplete.highlightNonMatches = "false"; + + isnot(style.getPropertyCSSValue("color").cssText, "rgb(255, 0, 0)", + "nomatch and not highlightNonMatches - should not be red"); + + ok(!autocomplete.popup.hasAttribute("autocompleteinput"), + "autocompleteinput on popup not set when closed"); + + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete3.xul b/toolkit/content/tests/chrome/test_autocomplete3.xul new file mode 100644 index 0000000000..953fd15c86 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete3.xul @@ -0,0 +1,188 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 3" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<textbox id="autocomplete" type="autocomplete" + autocompletesearch="simple" + onsearchcomplete="checkResult();"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +// Set to indicate whether or not we want autoCompleteSimple to return a result +var returnResult = true; + +const ACR = Components.interfaces.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (returnResult) { + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "Result"; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return this._param; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function() { return this.getValueAt(); }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + startSearch: function(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var componentManager = Components.manager + .QueryInterface(Components.interfaces.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + + +// Test Bug 325842 - completeDefaultIndex + +SimpleTest.waitForExplicitFinish(); +setTimeout(startTest, 0); + +var currentTest = 0; + +// Note the entries for these tests (key) are incremental. +const tests = [ + { completeDefaultIndex: "false", key: "r", result: "r", + start: 1, end: 1 }, + { completeDefaultIndex: "true", key: "e", result: "result", + start: 2, end: 6 }, + { completeDefaultIndex: "true", key: "t", result: "ret >> Result", + start: 3, end: 13 } +]; + +function startTest() { + var autocomplete = $("autocomplete"); + + // These should not be set by default. + is(autocomplete.hasAttribute("completedefaultindex"), false, + "completedefaultindex not set by default"); + + autocomplete.completeDefaultIndex = "true"; + + is(autocomplete.getAttribute("completedefaultindex"), "true", + "completedefaultindex attribute set correctly"); + is(autocomplete.completeDefaultIndex, true, + "autoFill getter returned correctly"); + + autocomplete.completeDefaultIndex = "false"; + + is(autocomplete.getAttribute("completedefaultindex"), "false", + "completedefaultindex attribute set to false correctly"); + is(autocomplete.completeDefaultIndex, false, + "completeDefaultIndex getter returned false correctly"); + + checkNext(); +} + +function checkNext() { + var autocomplete = $("autocomplete"); + + autocomplete.completeDefaultIndex = tests[currentTest].completeDefaultIndex; + autocomplete.focus(); + + synthesizeKey(tests[currentTest].key, {}); +} + +function checkResult() { + var autocomplete = $("autocomplete"); + var style = window.getComputedStyle(autocomplete, ""); + + is(autocomplete.value, tests[currentTest].result, + "Test " + currentTest + ": autocomplete.value should equal '" + + tests[currentTest].result + "'"); + + is(autocomplete.selectionStart, tests[currentTest].start, + "Test " + currentTest + ": autocomplete selection should start at " + + tests[currentTest].start); + + is(autocomplete.selectionEnd, tests[currentTest].end, + "Test " + currentTest + ": autocomplete selection should end at " + + tests[currentTest].end); + + ++currentTest; + + if (currentTest < tests.length) + setTimeout(checkNext, 0); + else { + // TODO (bug 494809): Autocomplete-in-the-middle should take in count RTL + // and complete on VK_RIGHT or VK_LEFT based on that. It should also revert + // what user has typed to far if he moves in the opposite direction. + if (autocomplete.value.indexOf(">>") == -1) { + // Test result if user accepts autocomplete suggestion. + synthesizeKey("VK_RIGHT", {}); + is(autocomplete.value, "Result", + "Test complete: autocomplete.value should equal 'Result'"); + is(autocomplete.selectionStart, 6, + "Test complete: autocomplete selection should start at 6"); + is(autocomplete.selectionEnd, 6, + "Test complete: autocomplete selection should end at 6"); + } + + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete4.xul b/toolkit/content/tests/chrome/test_autocomplete4.xul new file mode 100644 index 0000000000..007e956612 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete4.xul @@ -0,0 +1,280 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 4" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<textbox id="autocomplete" + type="autocomplete" + completedefaultindex="true" + + onsearchcomplete="searchComplete();" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +// Set to indicate whether or not we want autoCompleteSimple to return a result +var returnResult = true; + +const ACR = Components.interfaces.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (returnResult) { + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "Result"; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return this._param; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function() { return this.getValueAt(); }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + startSearch: function(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var componentManager = Components.manager + .QueryInterface(Components.interfaces.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + + +// Test Bug 325842 - completeDefaultIndex + +SimpleTest.waitForExplicitFinish(); +setTimeout(nextTest, 0); + +var currentTest = null; + +// Note the entries for these tests (key) are incremental. +const tests = [ + { + desc: "HOME key remove selection", + key: "VK_HOME", + removeSelection: true, + result: "re", + start: 0, end: 0 + }, + { + desc: "LEFT key remove selection", + key: "VK_LEFT", + removeSelection: true, + result: "re", + start: 1, end: 1 + }, + { desc: "RIGHT key remove selection", + key: "VK_RIGHT", + removeSelection: true, + result: "re", + start: 2, end: 2 + }, + { desc: "ENTER key remove selection", + key: "VK_RETURN", + removeSelection: true, + result: "re", + start: 2, end: 2 + }, + { + desc: "HOME key", + key: "VK_HOME", + removeSelection: false, + result: "Result", + start: 0, end: 0 + }, + { + desc: "LEFT key", + key: "VK_LEFT", + removeSelection: false, + result: "Result", + start: 5, end: 5 + }, + { desc: "RIGHT key", + key: "VK_RIGHT", + removeSelection: false, + result: "Result", + start: 6, end: 6 + }, + { desc: "RETURN key", + key: "VK_RETURN", + removeSelection: false, + result: "Result", + start: 6, end: 6 + }, + { desc: "TAB key should confirm suggestion when forcecomplete is set", + key: "VK_TAB", + removeSelection: false, + forceComplete: true, + result: "Result", + start: 6, end: 6 + }, + + { desc: "RIGHT key complete from middle", + key: "VK_RIGHT", + forceComplete: true, + completeFromMiddle: true, + result: "Result", + start: 6, end: 6 + }, + { + desc: "RIGHT key w/ minResultsForPopup=2", + key: "VK_RIGHT", + removeSelection: false, + minResultsForPopup: 2, + result: "Result", + start: 6, end: 6 + }, +]; + +function nextTest() { + if (!tests.length) { + // No more tests to run, finish. + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); + return; + } + + var autocomplete = $("autocomplete"); + autocomplete.value = ""; + currentTest = tests.shift(); + + // HOME key works differently on Mac, so we skip tests using it. + if (currentTest.key == "VK_HOME" && navigator.platform.indexOf("Mac") != -1) + nextTest(); + else + setTimeout(runCurrentTest, 0); +} + +function runCurrentTest() { + var autocomplete = $("autocomplete"); + if ("minResultsForPopup" in currentTest) + autocomplete.setAttribute("minresultsforpopup", currentTest.minResultsForPopup) + else + autocomplete.removeAttribute("minresultsforpopup"); + + autocomplete.focus(); + + if (!currentTest.completeFromMiddle) { + synthesizeKey("r", {}); + synthesizeKey("e", {}); + } + else { + synthesizeKey("l", {}); + synthesizeKey("t", {}); + } +} + +function searchComplete() { + var autocomplete = $("autocomplete"); + autocomplete.setAttribute("forcecomplete", currentTest.forceComplete ? true : false); + + if (currentTest.completeFromMiddle) { + if (!currentTest.forceComplete) { + synthesizeKey(currentTest.key, {}); + } + else if (!/ >> /.test(autocomplete.value)) { + // At this point we should have a value like "lt >> Result" showing. + throw new Error("Expected an middle-completed value, got " + autocomplete.value); + } + + // For forceComplete a blur should cause a value from the results to get + // completed to. E.g. "lt >> Result" will turn into "Result". + if (currentTest.forceComplete) + autocomplete.blur(); + + checkResult(); + return; + } + + is(autocomplete.value, "result", + "Test '" + currentTest.desc + "': autocomplete.value should equal 'result'"); + + if (autocomplete.selectionStart == 2) { // Finished inserting "re" string. + if (currentTest.removeSelection) { + // remove current selection + synthesizeKey("VK_DELETE", {}); + } + + synthesizeKey(currentTest.key, {}); + + checkResult(); + } +} + +function checkResult() { + var autocomplete = $("autocomplete"); + + is(autocomplete.value, currentTest.result, + "Test '" + currentTest.desc + "': autocomplete.value should equal '" + + currentTest.result + "'"); + + is(autocomplete.selectionStart, currentTest.start, + "Test '" + currentTest.desc + "': autocomplete selection should start at " + + currentTest.start); + + is(autocomplete.selectionEnd, currentTest.end, + "Test '" + currentTest.desc + "': autocomplete selection should end at " + + currentTest.end); + + setTimeout(nextTest, 0); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete5.xul b/toolkit/content/tests/chrome/test_autocomplete5.xul new file mode 100644 index 0000000000..2f6dc5a307 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete5.xul @@ -0,0 +1,152 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 5" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<textbox id="autocomplete" type="autocomplete" + autocompletesearch="simple" + ontextentered="checkTextEntered();" + ontextreverted="checkTextReverted();" + onsearchbegin="checkSearchBegin();" + onsearchcomplete="checkSearchCompleted();"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +const ACR = Components.interfaces.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "SUCCESS"; +} + +nsAutoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return this._param; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function() { return this.getValueAt(); }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + startSearch: function(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var componentManager = Components.manager + .QueryInterface(Components.interfaces.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +SimpleTest.waitForExplicitFinish(); +setTimeout(startTest, 0); + +function startTest() { + let autocomplete = $("autocomplete"); + + // blur the field to ensure that the popup is closed and that the previous + // search has stopped, then start a new search. + autocomplete.blur(); + autocomplete.focus(); + synthesizeKey("r", {}); +} + +let hasTextEntered = false; +let hasSearchBegun = false; + +function checkSearchBegin() { + hasSearchBegun = true; +} + +let test = 0; +function checkSearchCompleted() { + is(hasSearchBegun, true, "onsearchbegin handler has been correctly called."); + + if (test == 0) { + hasSearchBegun = false; + synthesizeKey("VK_RETURN", { }); + } else if (test == 1) { + hasSearchBegun = false; + synthesizeKey("VK_ESCAPE", { }); + } else { + throw "checkSearchCompleted should only be called twice."; + } +} + +function checkTextEntered() { + is(test, 0, "checkTextEntered should be reached from first test."); + is(hasSearchBegun, false, "onsearchbegin handler should not be called on text revert."); + + // fire second test + test++; + + let autocomplete = $("autocomplete"); + autocomplete.textValue = ""; + autocomplete.blur(); + autocomplete.focus(); + synthesizeKey("r", {}); +} + +function checkTextReverted() { + is(test, 1, "checkTextReverted should be the second test reached."); + is(hasSearchBegun, false, "onsearchbegin handler should not be called on text revert."); + + setTimeout(function() { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + }, 0); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul b/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul new file mode 100644 index 0000000000..19f54ac217 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul @@ -0,0 +1,128 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test 4" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://global/content/globalOverlay.js"/> + +<textbox id="autocomplete" + type="autocomplete" + completedefaultindex="true" + onsearchcomplete="searchComplete();" + timeout="0" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function autoCompleteSimpleResult(aString) { + this.searchString = aString; + this.searchResult = Components.interfaces.nsIAutoCompleteResult.RESULT_SUCCESS; + this.matchCount = 1; + this._param = "Result"; +} +autoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: Components.interfaces.nsIAutoCompleteResult.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return this._param; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function() { return this.getValueAt(); }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that returns one result. +let autoCompleteSimple = { + classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"), + contractID: "@mozilla.org/autocomplete/search;1?name=simple", + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIFactory, + Components.interfaces.nsIAutoCompleteSearch + ]), + createInstance: function (outer, iid) { + return this.QueryInterface(iid); + }, + + registerFactory: function () { + let registrar = + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + registrar.registerFactory(this.classID, "Test Simple Autocomplete", + this.contractID, this); + }, + unregisterFactory: function () { + let registrar = + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + registrar.unregisterFactory(this.classID, this); + }, + + startSearch: function (aString, aParam, aResult, aListener) { + let result = new autoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + stopSearch: function () {} +}; + +SimpleTest.waitForExplicitFinish(); + +// XPFE AutoComplete needs to register early. +autoCompleteSimple.registerFactory(); + +let gACTimer; +let gAutoComplete; + +function searchComplete() { + is(gAutoComplete.value, "result", "Value should be autocompleted now"); + ok(Date.now() - gACTimer > 500, "There should be a delay before autocomplete"); + + // Unregister the factory so that we don't get in the way of other tests + autoCompleteSimple.unregisterFactory(); + SimpleTest.finish(); +} + +function runTest() { + gAutoComplete = $("autocomplete"); + + const SEARCH_STRING = "res"; + + function cbCallback() { + gAutoComplete.focus(); + synthesizeKey("v", { accelKey: true }); + is(gAutoComplete.value, SEARCH_STRING, "Value should not be autocompleted immediately"); + } + + SimpleTest.waitForClipboard(SEARCH_STRING, function () { + gACTimer = Date.now(); + Components.classes["@mozilla.org/widget/clipboardhelper;1"] + .getService(Components.interfaces.nsIClipboardHelper) + .copyStringToClipboard(SEARCH_STRING, Components.interfaces.nsIClipboard.kGlobalClipboard); + }, cbCallback, cbCallback); +} +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_emphasis.xul b/toolkit/content/tests/chrome/test_autocomplete_emphasis.xul new file mode 100644 index 0000000000..b162742f12 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_emphasis.xul @@ -0,0 +1,175 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete emphasis test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<textbox id="richautocomplete" type="autocomplete" + autocompletesearch="simple" + onsearchcomplete="checkSearchCompleted();" + autocompletepopup="richpopup"/> +<panel id="richpopup" type="autocomplete-richlistbox" noautofocus="true"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +const ACR = Components.interfaces.nsIAutoCompleteResult; + +// A global variable to hold the search result for the current search. +var resultText = ""; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + this.searchResult = ACR.RESULT_SUCCESS; + this.matchCount = 1; +} + +nsAutoCompleteSimpleResult.prototype = { + searchString: null, + searchResult: ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return resultText; }, + getCommentAt: function() { return this.getValueAt(); }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function() { return this.getValueAt(); }, + getLabelAt: function() { return this.getValueAt(); }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that returns the string contained in 'resultText'. +var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple" +var autoCompleteSimple = { + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + startSearch: function(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var componentManager = Components.manager + .QueryInterface(Components.interfaces.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, "Test Simple Autocomplete", + autoCompleteSimpleName, autoCompleteSimple); + +SimpleTest.waitForExplicitFinish(); +setTimeout(nextTest, 0); + +/* Test cases have the following attributes: + * - search: A search string, to be emphasized in the result. + * - result: A fixed result string, so we can hardcode the expected emphasis. + * - emphasis: A list of chunks that should be emphasized or not, in strict alternation. + * - emphasizeFirst: Whether the first element of 'emphasis' should be emphasized; + * The emphasis of the other elements is defined by the strict alternation rule. + */ +let testcases = [ + { search: "test", + result: "A test string", + emphasis: ["A ", "test", " string"], + emphasizeFirst: false + }, + { search: "tea two", + result: "Tea for two, and two for tea...", + emphasis: ["Tea", " for ", "two", ", and ", "two", " for ", "tea", "..."], + emphasizeFirst: true + }, + { search: "tat", + result: "tatatat", + emphasis: ["tatatat"], + emphasizeFirst: true + }, + { search: "cheval valise", + result: "chevalise", + emphasis: ["chevalise"], + emphasizeFirst: true + } +]; +let test = -1; +let currentTest = null; + +function nextTest() { + test++; + + if (test >= testcases.length) { + // Unregister the factory so that we don't get in the way of other tests + componentManager.unregisterFactory(autoCompleteSimpleID, autoCompleteSimple); + SimpleTest.finish(); + return; + } + + // blur the field to ensure that the popup is closed and that the previous + // search has stopped, then start a new search. + let autocomplete = $("richautocomplete"); + autocomplete.blur(); + autocomplete.focus(); + + currentTest = testcases[test]; + resultText = currentTest.result; + autocomplete.value = currentTest.search; + synthesizeKey("VK_DOWN", {}); +} + +function checkSearchCompleted() { + let autocomplete = $("richautocomplete"); + let result = autocomplete.popup.richlistbox.firstChild; + + for (let attribute of [result._titleText, result._urlText]) { + is(attribute.childNodes.length, currentTest.emphasis.length, + "The element should have the expected number of children."); + for (let i = 0; i < currentTest.emphasis.length; i++) { + let node = attribute.childNodes[i]; + // Emphasized parts strictly alternate. + if ((i % 2 == 0) == currentTest.emphasizeFirst) { + // Check that this part is correctly emphasized. + is(node.nodeName, "span", ". That child should be a span node"); + ok(node.classList.contains("ac-emphasize-text"), ". That child should be emphasized"); + is(node.textContent, currentTest.emphasis[i], ". That emphasis should be as expected."); + } else { + // Check that this part is _not_ emphasized. + is(node.nodeName, "#text", ". That child should be a text node"); + is(node.textContent, currentTest.emphasis[i], ". That text should be as expected."); + } + } + } + + setTimeout(nextTest, 0); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xul b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xul new file mode 100644 index 0000000000..21670215d4 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_mac_caret.xul @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test" + onload="setTimeout(keyCaretTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<textbox id="autocomplete" type="autocomplete"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function keyCaretTest() +{ + var autocomplete = $("autocomplete"); + + autocomplete.focus(); + checkKeyCaretTest("VK_UP", 0, 0, false, "no value up"); + checkKeyCaretTest("VK_DOWN", 0, 0, false, "no value down"); + + autocomplete.value = "Sample"; + + autocomplete.selectionStart = 3; + autocomplete.selectionEnd = 3; + checkKeyCaretTest("VK_UP", 0, 0, true, "value up with caret in middle"); + checkKeyCaretTest("VK_UP", 0, 0, false, "value up with caret in middle again"); + + autocomplete.selectionStart = 2; + autocomplete.selectionEnd = 2; + checkKeyCaretTest("VK_DOWN", 6, 6, true, "value down with caret in middle"); + checkKeyCaretTest("VK_DOWN", 6, 6, false, "value down with caret in middle again"); + + autocomplete.selectionStart = 1; + autocomplete.selectionEnd = 4; + checkKeyCaretTest("VK_UP", 0, 0, true, "value up with selection"); + + autocomplete.selectionStart = 1; + autocomplete.selectionEnd = 4; + checkKeyCaretTest("VK_DOWN", 6, 6, true, "value down with selection"); + + SimpleTest.finish(); +} + +function checkKeyCaretTest(key, expectedStart, expectedEnd, result, testid) +{ + var autocomplete = $("autocomplete"); + + var event = result ? "keypress" : "!keypress"; + synthesizeKeyExpectEvent(key, { }, autocomplete.inputField, event, testid); + is(autocomplete.selectionStart, expectedStart, testid + " selectionStart"); + is(autocomplete.selectionEnd, expectedEnd, testid + " selectionEnd"); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xul b/toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xul new file mode 100644 index 0000000000..01004327de --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_placehold_last_complete.xul @@ -0,0 +1,309 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Autocomplete Widget Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="runTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://global/content/globalOverlay.js"/> + +<textbox id="autocomplete" + type="autocomplete" + completedefaultindex="true" + timeout="0" + autocompletesearch="simple"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function autoCompleteSimpleResult(aString, searchId) { + this.searchString = aString; + this.searchResult = Components.interfaces.nsIAutoCompleteResult.RESULT_SUCCESS; + this.matchCount = 1; + if (aString.startsWith('ret')) { + this._param = autoCompleteSimpleResult.retireCompletion; + } else { + this._param = "Result"; + } + this._searchId = searchId; +} +autoCompleteSimpleResult.retireCompletion = "Retire"; +autoCompleteSimpleResult.prototype = { + _param: "", + searchString: null, + searchResult: Components.interfaces.nsIAutoCompleteResult.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt: function() { return this._param; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +var searchCounter = 0; + +// A basic autocomplete implementation that returns one result. +let autoCompleteSimple = { + classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"), + contractID: "@mozilla.org/autocomplete/search;1?name=simple", + searchAsync: false, + pendingSearch: null, + + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIFactory, + Components.interfaces.nsIAutoCompleteSearch + ]), + createInstance: function (outer, iid) { + return this.QueryInterface(iid); + }, + + registerFactory: function () { + let registrar = + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + registrar.registerFactory(this.classID, "Test Simple Autocomplete", + this.contractID, this); + }, + unregisterFactory: function () { + let registrar = + Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); + registrar.unregisterFactory(this.classID, this); + }, + + startSearch: function (aString, aParam, aResult, aListener) { + let result = new autoCompleteSimpleResult(aString); + + if (this.searchAsync) { + // Simulate an async search by using a timeout before invoking the + // |onSearchResult| callback. + // Store the searchTimeout such that it can be canceled if stopSearch is called. + this.pendingSearch = setTimeout(() => { + this.pendingSearch = null; + + aListener.onSearchResult(this, result); + + // Move to the next step in the async test. + asyncTest.next(); + }, 0); + } else { + aListener.onSearchResult(this, result); + } + }, + stopSearch: function () { + clearTimeout(this.pendingSearch); + } +}; + +SimpleTest.waitForExplicitFinish(); + +// XPFE AutoComplete needs to register early. +autoCompleteSimple.registerFactory(); + +let gACTimer; +let gAutoComplete; +let asyncTest; + +let searchCompleteTimeoutId = null; + +function finishTest() { + // Unregister the factory so that we don't get in the way of other tests + autoCompleteSimple.unregisterFactory(); + SimpleTest.finish(); +} + +function runTest() { + gAutoComplete = $("autocomplete"); + gAutoComplete.focus(); + + // Return the search results synchronous, which also makes the completion + // happen synchronous. + autoCompleteSimple.searchAsync = false; + + synthesizeKey("r", {}); + is(gAutoComplete.value, "result", "Value should be autocompleted immediately"); + + synthesizeKey("e", {}); + is(gAutoComplete.value, "result", "Value should be autocompleted immediately"); + + synthesizeKey("VK_DELETE", {}); + is(gAutoComplete.value, "re", "Deletion should not complete value"); + + synthesizeKey("VK_BACK_SPACE", {}); + is(gAutoComplete.value, "r", "Backspace should not complete value"); + + synthesizeKey("VK_LEFT", {}); + is(gAutoComplete.value, "r", "Value should stay same when navigating with cursor"); + + runAsyncTest(); +} + +function* asyncTestGenerator() { + synthesizeKey("r", {}); + synthesizeKey("e", {}); + is(gAutoComplete.value, "re", "Value should not be autocompleted immediately"); + + // Calling |yield undefined| makes this generator function wait until + // |asyncTest.next();| is called. This happens from within the + // |autoCompleteSimple.startSearch()| function once the simulated async + // search has finished. + // Therefore, the effect of the |yield undefined;| here (and the ones) below + // is to wait until the async search result comes back. + yield undefined; + + is(gAutoComplete.value, "result", "Value should be autocompleted"); + + // Test if typing the `s` character completes directly based on the last + // completion + synthesizeKey("s", {}); + is(gAutoComplete.value, "result", "Value should be completed immediately"); + + yield undefined; + + is(gAutoComplete.value, "result", "Value should be autocompleted to same value"); + synthesizeKey("VK_DELETE", {}); + is(gAutoComplete.value, "res", "Deletion should not complete value"); + + // No |yield undefined| needed here as no completion is triggered by the deletion. + + is(gAutoComplete.value, "res", "Still no complete value after deletion"); + + synthesizeKey("VK_BACK_SPACE", {}); + is(gAutoComplete.value, "re", "Backspace should not complete value"); + + yield undefined; + + is(gAutoComplete.value, "re", "Value after search due to backspace should stay the same"); (3) + + // Typing a character that is not like the previous match. In this case, the + // completion cannot happen directly and therefore the value will be completed + // only after the search has finished. + synthesizeKey("t", {}); + is(gAutoComplete.value, "ret", "Value should not be autocompleted immediately"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + synthesizeKey("i", {}); + is(gAutoComplete.value, "retire", "Value should be autocompleted immediately"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted to the same value"); + + // Setup the scene to test how the completion behaves once the placeholder + // completion and the result from the search do not agree with each other. + gAutoComplete.value = 'r'; + // Need to type two characters as the input was reset and the autocomplete + // controller things, ther user hit the backspace button, in which case + // no completion is performed. But as a completion is desired, another + // character `t` is typed afterwards. + synthesizeKey("e", {}); + yield undefined; + synthesizeKey("t", {}); + is(gAutoComplete.value, "ret", "Value should not be autocompleted"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + // The placeholder string is now set to "retire". Changing the completion + // string to "retirement" and see what the completion will turn out like. + autoCompleteSimpleResult.retireCompletion = "Retirement"; + synthesizeKey("i", {}); + is(gAutoComplete.value, "retire", "Value should be autocompleted based on placeholder"); + + yield undefined; + + is(gAutoComplete.value, "retirement", "Value should be autocompleted based on search result"); + + // Change the search result to `Retire` again and see if the new result is + // complited. + autoCompleteSimpleResult.retireCompletion = "Retire"; + synthesizeKey("r", {}); + is(gAutoComplete.value, "retirement", "Value should be autocompleted based on placeholder"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted based on search result"); + + // Complete the value + gAutoComplete.value = 're'; + synthesizeKey("t", {}); + yield undefined; + synthesizeKey("i", {}); + is(gAutoComplete.value, "reti", "Value should not be autocompleted"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + // Remove the selected text "re" (1) and the "et" (2). Afterwards, add it again (3). + // This should not cause the completion to kick in. + synthesizeKey("VK_DELETE", {}); // (1) + + is(gAutoComplete.value, "reti", "Value should not complete after deletion"); + + gAutoComplete.selectionStart = 1; + gAutoComplete.selectionEnd = 3; + synthesizeKey("VK_DELETE", {}); // (2) + + is(gAutoComplete.value, "ri", "Value should stay unchanged after removing character in the middle"); + + yield undefined; + + synthesizeKey("e", {}); // (3.1) + is(gAutoComplete.value, "rei", "Inserting a character in the middle should not complete the value"); + + yield undefined; + + synthesizeKey("t", {}); // (3.2) + is(gAutoComplete.value, "reti", "Inserting a character in the middle should not complete the value"); + + yield undefined; + + // Adding a new character at the end should not cause the completion to happen again + // as the completion failed before. + gAutoComplete.selectionStart = 4; + gAutoComplete.selectionEnd = 4; + synthesizeKey("r", {}); + is(gAutoComplete.value, "retir", "Value should not be autocompleted immediately"); + + yield undefined; + + is(gAutoComplete.value, "retire", "Value should be autocompleted"); + + finishTest(); + yield undefined; +} + +function runAsyncTest() { + gAutoComplete.value = ''; + autoCompleteSimple.searchAsync = true; + + asyncTest = asyncTestGenerator(); + asyncTest.next(); +} +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html new file mode 100644 index 0000000000..3f57a0d6e4 --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_input.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>autocomplete with composition tests on HTML input element</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="file_autocomplete_with_composition.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <iframe id="formTarget" name="formTarget"></iframe> + <form action="data:text/html," target="formTarget"> + <input name="test" id="input"><input type="submit"> + </form> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + var formFillController = + SpecialPowers.getFormFillController() + .QueryInterface(Components.interfaces.nsIAutoCompleteInput); + var originalFormFillTimeout = formFillController.timeout; + + SpecialPowers.attachFormFillControllerTo(window); + var target = document.getElementById("input"); + + // Register a word to the form history. + target.focus(); + target.value = "Mozilla"; + synthesizeKey("VK_RETURN", {}); + target.value = ""; + + var test1 = new nsDoTestsForAutoCompleteWithComposition( + "Testing on HTML input (asynchronously search)", + window, target, formFillController.controller, is, + function () { return target.value; }, + function () { + target.setAttribute("timeout", 0); + var test2 = new nsDoTestsForAutoCompleteWithComposition( + "Testing on HTML input (synchronously search)", + window, target, formFillController.controller, is, + function () { return target.value; }, + function () { + formFillController.timeout = originalFormFillTimeout; + SpecialPowers.detachFormFillControllerFrom(window); + SimpleTest.finish(); + }); + }); +} + +SimpleTest.waitForFocus(runTests); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul new file mode 100644 index 0000000000..90972b8dab --- /dev/null +++ b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul @@ -0,0 +1,124 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Testing autocomplete with composition" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + <script type="text/javascript" + src="file_autocomplete_with_composition.js" /> + + <textbox id="textbox" type="autocomplete" + autocompletesearch="simpleForComposition"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +const nsIAutoCompleteResult = Components.interfaces.nsIAutoCompleteResult; + +// This result can't be constructed in-line, because otherwise we leak memory. +function nsAutoCompleteSimpleResult(aString) +{ + this.searchString = aString; + if (aString == "" || aString == "Mozilla".substr(0, aString.length)) { + this.searchResult = nsIAutoCompleteResult.RESULT_SUCCESS; + this.matchCount = 1; + this._value = "Mozilla"; + } else { + this.searchResult = nsIAutoCompleteResult.RESULT_NOMATCH; + this.matchCount = 0; + this._value = ""; + } +} + +nsAutoCompleteSimpleResult.prototype = { + _value: "", + searchString: null, + searchResult: nsIAutoCompleteResult.RESULT_FAILURE, + defaultIndex: 0, + errorDescription: null, + matchCount: 0, + getValueAt: function(aIndex) { return aIndex == 0 ? this._value : null; }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return null; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function(aIndex) { return this.getValueAt(aIndex); }, + getLabelAt: function() { return null; }, + removeValueAt: function() {} +}; + +// A basic autocomplete implementation that either returns one result or none +var autoCompleteSimpleID = + Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"); +var autoCompleteSimpleName = + "@mozilla.org/autocomplete/search;1?name=simpleForComposition" +var autoCompleteSimple = { + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsIFactory) || + iid.equals(Components.interfaces.nsIAutoCompleteSearch)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function(outer, iid) { + return this.QueryInterface(iid); + }, + + startSearch: function(aString, aParam, aResult, aListener) { + var result = new nsAutoCompleteSimpleResult(aString); + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var componentManager = + Components.manager + .QueryInterface(Components.interfaces.nsIComponentRegistrar); +componentManager.registerFactory(autoCompleteSimpleID, + "Test Simple Autocomplete for composition", + autoCompleteSimpleName, autoCompleteSimple); + +function runTests() +{ + var target = document.getElementById("textbox"); + target.setAttribute("timeout", 1); + var test1 = new nsDoTestsForAutoCompleteWithComposition( + "Testing on XUL textbox (asynchronously search)", + window, target, target.controller, is, + function () { return target.value; }, + function () { + target.setAttribute("timeout", 0); + var test2 = new nsDoTestsForAutoCompleteWithComposition( + "Testing on XUL textbox (synchronously search)", + window, target, target.controller, is, + function () { return target.value; }, + function () { + // Unregister the factory so that we don't get in the way of other + // tests + componentManager.unregisterFactory(autoCompleteSimpleID, + autoCompleteSimple); + SimpleTest.finish(); + }); + }); +} + +SimpleTest.waitForFocus(runTests); +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_browser_drop.xul b/toolkit/content/tests/chrome/test_browser_drop.xul new file mode 100644 index 0000000000..4ba21c514c --- /dev/null +++ b/toolkit/content/tests/chrome/test_browser_drop.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Browser Drop Test" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"/> + + <script><![CDATA[ +SimpleTest.waitForExplicitFinish(); +function runTest() { + add_task(function*() { + let win = window.open("window_browser_drop.xul", "_blank", "chrome,width=200,height=200"); + yield SimpleTest.promiseFocus(win); + for (let browserType of ["content", "remote-content"]) { + yield win.dropLinksOnBrowser(win.document.getElementById(browserType + "child"), browserType); + } + yield win.dropLinksOnBrowser(win.document.getElementById("chromechild"), "chrome"); + }); +} +//]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug1048178.xul b/toolkit/content/tests/chrome/test_bug1048178.xul new file mode 100644 index 0000000000..79f3acad5d --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug1048178.xul @@ -0,0 +1,86 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1048178 +--> +<window title="Mozilla Bug 1048178" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"/> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1048178" + target="_blank">Mozilla Bug 1048178</a> + + <hbox> + <scrollbar id="scroller" + orient="horizontal" + curpos="0" + maxpos="500" + pageincrement="500" + width="500" + style="margin:0"/> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 1048178 **/ +var scrollbarTester = { + scrollbar: null, + startTest: function() { + this.scrollbar = $("scroller"); + this.setScrollToClick(false); + this.testThumbDragging(); + SimpleTest.finish(); + }, + testThumbDragging: function() { + var x = 400; // on the right half of the scroolbar + var y = 5; + + this.mousedown(x, y, 0); + this.mousedown(x, y, 2); + this.mouseup(x, y, 2); + this.mouseup(x, y, 0); + + var newPos = this.getPos(); // sould be '500' + + this.mousedown(x, y, 0); + this.mousemove(x-1, y, 0); + this.mouseup(x-1, y, 0); + + var newPos2 = this.getPos(); + ok(newPos2 < newPos, + "Scrollbar thumb should follow the mouse when dragged."); + }, + setScrollToClick: function(value) { + var prefService = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + var uiBranch = prefService.getBranch("ui."); + uiBranch.setIntPref("scrollToClick", value ? 1 : 0); + }, + getPos: function() { + return this.scrollbar.getAttribute("curpos"); + }, + mousedown: function(x, y, button) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousedown", 'button': button }); + }, + mousemove: function(x, y, button) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousemove", 'button': button }); + }, + mouseup: function(x, y, button) { + synthesizeMouse(this.scrollbar, x, y, { type: "mouseup", 'button': button }); + } +} + +function doTest() { + setTimeout(function() { scrollbarTester.startTest(); }, 0); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug253481.xul b/toolkit/content/tests/chrome/test_bug253481.xul new file mode 100644 index 0000000000..aa5c017c68 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug253481.xul @@ -0,0 +1,90 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=253481 +--> +<window title="Mozilla Bug 253481" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=253481">Mozilla Bug 253481</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +</body> + +<description> + Tests pasting of multi-line content into a single-line xul:textbox. +</description> + +<vbox> +<textbox id="pasteintact" newlines="pasteintact"/> +<textbox id="pastetofirst" newlines="pastetofirst"/> +<textbox id="replacewithspaces" newlines="replacewithspaces"/> +<textbox id="strip" newlines="strip"/> +<textbox id="replacewithcommas" newlines="replacewithcommas"/> +<textbox id="stripsurroundingwhitespace" newlines="stripsurroundingwhitespace"/> +</vbox> +<script class="testbody" type="application/javascript;version=1.7"> +<![CDATA[ +/** Test for Bug 253481 **/ +function testPaste(name, element, expected) { + element.value = ""; + element.focus(); + synthesizeKey("v", { accelKey: true }); + is(element.value, expected, name); +} + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { +setTimeout(function() { +var testString = "\n hello hello \n world\nworld \n"; +var expectedResults = { +// even "pasteintact" strips leading/trailing newlines +"pasteintact": testString.replace(/^\n/, '').replace(/\n$/, ''), +// "pastetofirst" strips leading newlines +"pastetofirst": testString.replace(/^\n/, '').split(/\n/)[0], +// "replacewithspaces" strips trailing newlines first - bug 432415 +"replacewithspaces": testString.replace(/\n$/, '').replace(/\n/g,' '), +// "strip" is pretty straightforward +"strip": testString.replace(/\n/g,''), +// "replacewithcommas" strips leading and trailing newlines first +"replacewithcommas": testString.replace(/^\n/, '').replace(/\n$/, '').replace(/\n/g,','), +// "stripsurroundingwhitespace" strips all newlines and whitespace around them +"stripsurroundingwhitespace": testString.replace(/\s*\n\s*/g,'') +}; + +// Put a multi-line string in the clipboard +SimpleTest.waitForClipboard(testString, function() { + var clip = Components.classes["@mozilla.org/widget/clipboardhelper;1"] + .getService(Components.interfaces.nsIClipboardHelper); + clip.copyString(testString); +}, function() { + for (let [item, expected] of Object.entries(expectedResults)) { + testPaste(item, $(item), expected); + } + + SimpleTest.finish(); +}, function() { + ok(false, "Could not copy the string to clipboard, giving up"); + + SimpleTest.finish(); +}); +}, 0); +}); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug263683.xul b/toolkit/content/tests/chrome/test_bug263683.xul new file mode 100644 index 0000000000..c5755c9f17 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug263683.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=263683 +--> +<window title="Mozilla Bug 263683" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=263683"> + Mozilla Bug 263683 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 263683 **/ + SimpleTest.waitForExplicitFinish(); + window.open("bug263683_window.xul", "263683test", + "chrome,width=600,height=600"); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug304188.xul b/toolkit/content/tests/chrome/test_bug304188.xul new file mode 100644 index 0000000000..f41d24f9bb --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug304188.xul @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=304188 +--> +<window title="Mozilla Bug 304188" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=304188">Mozilla Bug 304188</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 304188 **/ +SimpleTest.waitForExplicitFinish(); +window.open("bug304188_window.xul", "findbartest", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug331215.xul b/toolkit/content/tests/chrome/test_bug331215.xul new file mode 100644 index 0000000000..e0d0d1e0a9 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug331215.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=331215 +--> +<window title="Mozilla Bug 331215" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=331215">Mozilla Bug 331215</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 331215 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug331215_window.xul", "331215test", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug360220.xul b/toolkit/content/tests/chrome/test_bug360220.xul new file mode 100644 index 0000000000..3fcb2bf139 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug360220.xul @@ -0,0 +1,61 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=360220 +--> +<window title="Mozilla Bug 360220" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=360220">Mozilla Bug 360220</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<menulist id="menulist"> + <menupopup> + <menuitem id="firstItem" label="foo" selected="true"/> + <menuitem id="secondItem" label="bar"/> + </menupopup> +</menulist> +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +/** Test for Bug 360220 **/ + +var menulist = document.getElementById("menulist"); +var secondItem = document.getElementById("secondItem"); +menulist.selectedItem = secondItem; + +is(menulist.label, "bar", "second item was not selected"); + +let mutObserver = new MutationObserver(() => { + is(menulist.label, "new label", "menulist label was not updated to the label of its selected item"); + done(); +}); +mutObserver.observe(menulist, { attributeFilter: ['label'] }); +secondItem.label = "new label"; + +let failureTimeout = setTimeout(function() { + ok(false, "menulist label should have updated"); + done(); +}, 2000); + +function done() { + mutObserver.disconnect(); + clearTimeout(failureTimeout); + SimpleTest.finish(); +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug360437.xul b/toolkit/content/tests/chrome/test_bug360437.xul new file mode 100644 index 0000000000..eb17adcf5d --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug360437.xul @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=360437 +--> +<window title="Mozilla Bug 360437" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=360437">Mozilla Bug 360437</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 360437 **/ +SimpleTest.waitForExplicitFinish(); +window.open("bug360437_window.xul", "360437test", + "chrome,width=600,height=600"); + + + + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug365773.xul b/toolkit/content/tests/chrome/test_bug365773.xul new file mode 100644 index 0000000000..17385365ad --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug365773.xul @@ -0,0 +1,67 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=365773
+-->
+<window title="Mozilla Bug 365773"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=365773">Mozilla Bug 365773</a>
+<p id="display">
+ <radiogroup id="group" collapsed="true" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <radio id="item" label="Item"/>
+ </radiogroup>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 365773 **/
+
+function selectItem(item, isIndex, testName) {
+ var exception = null;
+ try {
+ if (isIndex)
+ document.getElementById("group").selectedIndex = item;
+ else
+ document.getElementById("group").selectedItem = item;
+ }
+ catch(e) {
+ exception = e;
+ }
+
+ ok(exception == null, testName);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function runTests() {
+ var item = document.getElementById("item");
+
+ selectItem(item, false, "Radio button selected with selectedItem (not focused)");
+ selectItem(null, false, "Radio button deselected with selectedItem (not focused)");
+ selectItem(0, true, "Radio button selected with selectedIndex (not focused)");
+ selectItem(-1, true, "Radio button deselected with selectedIndex (not focused)");
+
+ document.getElementById("group").focus();
+
+ selectItem(item, false, "Radio button selected with selectedItem (focused)");
+ selectItem(null, false, "Radio button deselected with selectedItem (focused)");
+ selectItem(0, true, "Radio button selected with selectedIndex (focused)");
+ selectItem(-1, true, "Radio button deselected with selectedIndex (focused)");
+
+ SimpleTest.finish();
+};
+]]>
+</script>
+
+</window>
diff --git a/toolkit/content/tests/chrome/test_bug366992.xul b/toolkit/content/tests/chrome/test_bug366992.xul new file mode 100644 index 0000000000..2c92defc54 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug366992.xul @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=366992 +--> +<window title="Mozilla Bug 366992" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=366992">Mozilla Bug 366992</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 366992 **/ +SimpleTest.waitForExplicitFinish(); +window.open("bug366992_window.xul", "findbartest", + "chrome,width=600,height=600"); + + + + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug382990.xul b/toolkit/content/tests/chrome/test_bug382990.xul new file mode 100644 index 0000000000..aa3b00431d --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug382990.xul @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=382990 +--> +<window title="Mozilla Bug 382990" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="startThisTest()"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=382990" + target="_blank">Mozilla Bug 382990</a> + </body> + + <tree id="testTree" height="200px"> + <treecols> + <treecol flex="1" label="Name" id="name"/> + </treecols> + <treechildren> + <treeitem><treerow><treecell label="a"/></treerow></treeitem> + <treeitem><treerow><treecell label="z"/></treerow></treeitem> + </treechildren> + </tree> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + /** Test for Bug 382990 **/ + + SimpleTest.waitForExplicitFinish(); + function startThisTest() + { + var treeElem = document.getElementById("testTree"); + treeElem.view.selection.select(0); + treeElem.focus(); + synthesizeKey("z", {ctrlKey: true}); + ok(!treeElem.view.selection.isSelected(1), "Tree selection should not change for key events with ctrl pressed."); + SimpleTest.finish(); + } + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug409624.xul b/toolkit/content/tests/chrome/test_bug409624.xul new file mode 100644 index 0000000000..59a862cad7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug409624.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=409624 +--> +<window title="Mozilla Bug 409624" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=409624"> + Mozilla Bug 409624 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 409624 **/ + SimpleTest.waitForExplicitFinish(); + window.open("bug409624_window.xul", "409624test", + "chrome,width=600,height=600"); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug418874.xul b/toolkit/content/tests/chrome/test_bug418874.xul new file mode 100644 index 0000000000..13f0a14530 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug418874.xul @@ -0,0 +1,71 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Textbox with placeholder test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <textbox id="t1" placeholder="empty"/> + </hbox> + + <hbox> + <textbox id="t2" placeholder="empty"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function doTest() { + var t1 = $("t1"); + var t2 = $("t2"); + setTextboxValue(t1, "1"); + var t1Enabled = {}; + var t1CanUndo = {}; + t1.editor.canUndo(t1Enabled, t1CanUndo); + is(t1CanUndo.value, true, + "undo correctly enabled when placeholder was not changed through property"); + + t2.placeholder = "reallyempty"; + setTextboxValue(t2, "2"); + var t2Enabled = {}; + var t2CanUndo = {}; + t2.editor.canUndo(t2Enabled, t2CanUndo); + is(t2CanUndo.value, true, + "undo correctly enabled when placeholder explicitly changed through property"); + + SimpleTest.finish(); + } + + function setTextboxValue(textbox, value) { + textbox.focus(); + for (var i = 0; i < value.length; ++i) { + synthesizeKey(value.charAt(i), {}); + } + textbox.blur(); + } + + SimpleTest.waitForFocus(doTest); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug429723.xul b/toolkit/content/tests/chrome/test_bug429723.xul new file mode 100644 index 0000000000..99ee8acd97 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug429723.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=429723 +--> +<window title="Mozilla Bug 429723" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=429723">Mozilla Bug 429723</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 429723 **/ +SimpleTest.waitForExplicitFinish(); +window.open("bug429723_window.xul", "429723test", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug437844.xul b/toolkit/content/tests/chrome/test_bug437844.xul new file mode 100644 index 0000000000..b194b30419 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug437844.xul @@ -0,0 +1,95 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=437844 +https://bugzilla.mozilla.org/show_bug.cgi?id=348233 +--> +<window title="Mozilla Bug 437844 and Bug 348233" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.js"></script> + <script type="application/javascript" + src="RegisterUnregisterChrome.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=437844"> + Mozilla Bug 437844 + </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=348233"> + Mozilla Bug 348233 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.expectAssertions(18, 22); + + /** Test for Bug 437844 and Bug 348233 **/ + SimpleTest.waitForExplicitFinish(); + + let prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + prefs.setCharPref("intl.uidirection.en-US", "rtl"); + + let rootDir = getRootDirectory(window.location.href); + let manifest = rootDir + "rtlchrome/rtl.manifest"; + + //copy rtlchrome to profile/rtlchrome and generate .manifest + let filePath = chromeURIToFile(manifest); + let tempProfileDir = copyDirToTempProfile(filePath.path, 'rtlchrome'); + if (tempProfileDir.path.lastIndexOf('\\') >= 0) { + manifest = "content rtlchrome /" + tempProfileDir.path.replace(/\\/g, '/') + "\n"; + } else { + manifest = "content rtlchrome " + tempProfileDir.path + "\n"; + } + manifest += "override chrome://global/locale/intl.css chrome://rtlchrome/content/rtlchrome/rtl.css\n"; + manifest += "override chrome://global/locale/global.dtd chrome://rtlchrome/content/rtlchrome/rtl.dtd\n"; + + let cleanupFunc = createManifestTemporarily(tempProfileDir, manifest); + + // Load about:plugins in an iframe + let frame = document.createElement("iframe"); + frame.setAttribute("src", "about:plugins"); + frame.addEventListener("load", function () { + frame.removeEventListener("load", arguments.callee, false); + is(frame.contentDocument.dir, "rtl", "about:plugins should be RTL in RTL locales"); + + let gDirSvc = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIDirectoryService). + QueryInterface(Components.interfaces.nsIProperties); + let tmpd = gDirSvc.get("ProfD", Components.interfaces.nsIFile); + + frame = document.createElement("iframe"); + frame.setAttribute("src", "file://" + tmpd.path); // a file:// URI, bug 348233 + frame.addEventListener("load", function () { + frame.removeEventListener("load", arguments.callee, false); + + is(frame.contentDocument.body.dir, "rtl", "file:// listings should be RTL in RTL locales"); + + cleanupFunc(); + prefs.clearUserPref("intl.uidirection.en-US"); + SimpleTest.finish(); + }, false); + document.documentElement.appendChild(frame); + }, false); + document.documentElement.appendChild(frame); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug451540.xul b/toolkit/content/tests/chrome/test_bug451540.xul new file mode 100644 index 0000000000..64ae43c3c8 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug451540.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=451540 +--> +<window title="Mozilla Bug 451540" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=451540"> + Mozilla Bug 451540 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 451540 **/ + SimpleTest.waitForExplicitFinish(); + window.open("bug451540_window.xul", "451540test", + "chrome,width=600,height=600"); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug457632.xul b/toolkit/content/tests/chrome/test_bug457632.xul new file mode 100644 index 0000000000..7bc70f2cc3 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug457632.xul @@ -0,0 +1,178 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 457632 + --> +<window title="Bug 457632" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <notificationbox id="nb"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" + onload="test()"/> + + <!-- test code goes here --> +<script type="application/javascript"> +<![CDATA[ +var gNotificationBox; + +function completeAnimation(nextTest) { + if (!gNotificationBox._animating) { + nextTest(); + return; + } + + setTimeout(completeAnimation, 50, nextTest); +} + +function test() { + SimpleTest.waitForExplicitFinish(); + gNotificationBox = document.getElementById("nb"); + + is(gNotificationBox.allNotifications.length, 0, "There should be no initial notifications"); + gNotificationBox.appendNotification("Test notification", + "notification1", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + is(gNotificationBox.allNotifications.length, 1, "Notification exists while animating in"); + let notification = gNotificationBox.getNotificationWithValue("notification1"); + ok(notification, "Notification should exist while animating in"); + + // Wait for the notificaton to finish displaying + completeAnimation(test1); +} + +// Tests that a notification that is fully animated in gets removed immediately +function test1() { + let notification = gNotificationBox.getNotificationWithValue("notification1"); + gNotificationBox.removeNotification(notification); + notification = gNotificationBox.getNotificationWithValue("notification1"); + ok(!notification, "Test 1 showed notification was still present"); + ok(!gNotificationBox.currentNotification, "Test 1 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 1 should show no notifications present"); + + // Wait for the notificaton to finish hiding + completeAnimation(test2); +} + +// Tests that a notification that is animating in gets removed immediately +function test2() { + let notification = gNotificationBox.appendNotification("Test notification", + "notification2", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + gNotificationBox.removeNotification(notification); + notification = gNotificationBox.getNotificationWithValue("notification2"); + ok(!notification, "Test 2 showed notification was still present"); + ok(!gNotificationBox.currentNotification, "Test 2 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 2 should show no notifications present"); + + // Get rid of the hiding notifications + gNotificationBox.removeAllNotifications(true); + test3(); +} + +// Tests that a background notification goes away immediately +function test3() { + let notification = gNotificationBox.appendNotification("Test notification", + "notification3", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + let notification2 = gNotificationBox.appendNotification("Test notification", + "notification4", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + is(gNotificationBox.allNotifications.length, 2, "Test 3 should show 2 notifications present"); + gNotificationBox.removeNotification(notification); + is(gNotificationBox.allNotifications.length, 1, "Test 3 should show 1 notifications present"); + notification = gNotificationBox.getNotificationWithValue("notification3"); + ok(!notification, "Test 3 showed notification was still present"); + gNotificationBox.removeNotification(notification2); + is(gNotificationBox.allNotifications.length, 0, "Test 3 should show 0 notifications present"); + notification2 = gNotificationBox.getNotificationWithValue("notification4"); + ok(!notification2, "Test 3 showed notification2 was still present"); + ok(!gNotificationBox.currentNotification, "Test 3 said there was still a current notification"); + + // Get rid of the hiding notifications + gNotificationBox.removeAllNotifications(true); + test4(); +} + +// Tests that a foreground notification hiding a background one goes away +function test4() { + let notification = gNotificationBox.appendNotification("Test notification", + "notification5", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + let notification2 = gNotificationBox.appendNotification("Test notification", + "notification6", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + gNotificationBox.removeNotification(notification2); + notification2 = gNotificationBox.getNotificationWithValue("notification6"); + ok(!notification2, "Test 4 showed notification2 was still present"); + is(gNotificationBox.currentNotification, notification, "Test 4 said the current notification was wrong"); + is(gNotificationBox.allNotifications.length, 1, "Test 4 should show 1 notifications present"); + gNotificationBox.removeNotification(notification); + notification = gNotificationBox.getNotificationWithValue("notification5"); + ok(!notification, "Test 4 showed notification was still present"); + ok(!gNotificationBox.currentNotification, "Test 4 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 4 should show 0 notifications present"); + + // Get rid of the hiding notifications + gNotificationBox.removeAllNotifications(true); + test5(); +} + +// Tests that removeAllNotifications gets rid of everything +function test5() { + let notification = gNotificationBox.appendNotification("Test notification", + "notification7", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + let notification2 = gNotificationBox.appendNotification("Test notification", + "notification8", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + gNotificationBox.removeAllNotifications(); + notification = gNotificationBox.getNotificationWithValue("notification7"); + notification2 = gNotificationBox.getNotificationWithValue("notification8"); + ok(!notification, "Test 5 showed notification was still present"); + ok(!notification2, "Test 5 showed notification2 was still present"); + ok(!gNotificationBox.currentNotification, "Test 5 said there was still a current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 5 should show 0 notifications present"); + + gNotificationBox.appendNotification("Test notification", + "notification9", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + + // Wait for the notificaton to finish displaying + completeAnimation(test6); +} + +// Tests whether removing an already removed notification doesn't break things +function test6() { + let notification = gNotificationBox.getNotificationWithValue("notification9"); + ok(notification, "Test 6 should have an initial notification"); + gNotificationBox.removeNotification(notification); + gNotificationBox.removeNotification(notification); + + ok(!gNotificationBox.currentNotification, "Test 6 shouldn't be any current notification"); + is(gNotificationBox.allNotifications.length, 0, "Test 6 allNotifications.length should be 0"); + notification = gNotificationBox.appendNotification("Test notification", + "notification10", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + is(notification, gNotificationBox.currentNotification, "Test 6 should have made the current notification"); + gNotificationBox.removeNotification(notification); + + SimpleTest.finish(); +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug460942.xul b/toolkit/content/tests/chrome/test_bug460942.xul new file mode 100644 index 0000000000..dae10da57a --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug460942.xul @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=460942 +--> +<window title="Mozilla Bug 460942" + onload="runTests()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=460942" + target="_blank">Mozilla Bug 460942</a> + </body> + + <!-- test code goes here --> + + <richlistbox> + <richlistitem id="item1"> + <label value="one"/> + <box> + <label value="two"/> + </box> + </richlistitem> + <richlistitem id="item2"><description>one</description><description>two</description></richlistitem> + </richlistbox> + + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 460942 **/ + function runTests() { + is ($("item1").label, "one two"); + is ($("item2").label, ""); + SimpleTest.finish(); + } + SimpleTest.waitForExplicitFinish(); + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug471776.xul b/toolkit/content/tests/chrome/test_bug471776.xul new file mode 100644 index 0000000000..6002c691ac --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug471776.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> + +<!-- 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/. --> + + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Textbox with placeholder undo test" width="500" height="600" + onload="doTest();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <textbox id="t1" placeholder="empty"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + function doTest() { + var t1 = $("t1"); + t1.focus(); + var t1Enabled = {}; + var t1CanUndo = {}; + t1.editor.canUndo(t1Enabled, t1CanUndo); + ok(!t1CanUndo.value, "undo correctly disabled when no user edits"); + SimpleTest.finish(); + } + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug509732.xul b/toolkit/content/tests/chrome/test_bug509732.xul new file mode 100644 index 0000000000..cc7ce6807f --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug509732.xul @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 509732 + --> +<window title="Bug 509732" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <notificationbox id="nb" hidden="true"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;" + onload="test()"/> + + <!-- test code goes here --> +<script type="application/javascript"> +<![CDATA[ +var gNotificationBox; + +// Tests that a notification that is added in an hidden box didn't throw the animation +function test() { + SimpleTest.waitForExplicitFinish(); + gNotificationBox = document.getElementById("nb"); + + is(gNotificationBox.allNotifications.length, 0, "There should be no initial notifications"); + + gNotificationBox.appendNotification("Test notification", + "notification1", null, + gNotificationBox.PRIORITY_INFO_LOW, + null); + + is(gNotificationBox.allNotifications.length, 1, "Notification exists"); + is(gNotificationBox._animating, false, "Notification shouldn't be animating"); + + test1(); +} + +// Tests that a notification that is removed from an hidden box didn't throw the animation +function test1() { + let notification = gNotificationBox.getNotificationWithValue("notification1"); + gNotificationBox.removeNotification(notification); + ok(!gNotificationBox.currentNotification, "Test 1 should show no current animation"); + is(gNotificationBox._animating, false, "Notification shouldn't be animating"); + is(gNotificationBox.allNotifications.length, 0, "Test 1 should show no notifications present"); + + SimpleTest.finish(); +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_bug554279.xul b/toolkit/content/tests/chrome/test_bug554279.xul new file mode 100644 index 0000000000..d4057b8907 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug554279.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Toolbar" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="startTest();"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <toolbox> + <toolbarpalette id="palette"/> + + <toolbar id="tb1" currentset="p1"/> + </toolbox> + + <!-- test resuls are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="text/javascript"><![CDATA[ + var toolbar = $("tb1"); + + ok(toolbar, "got the toolbar, triggering the xbl constructor"); + + var palette = $("palette"); + ok(palette, "palette is still in the document"); + + var button = document.createElement("p1"); + button.id = button.label = "p1"; + palette.appendChild(button); + + SimpleTest.waitForExplicitFinish(); + function startTest() { + is(button.parentNode, toolbar, "button has been added to the toolbar"); + SimpleTest.finish(); + } + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug557987.xul b/toolkit/content/tests/chrome/test_bug557987.xul new file mode 100644 index 0000000000..ba680568ff --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug557987.xul @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 557987 + --> +<window title="Bug 557987" width="400" height="400" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <toolbarbutton id="button" type="menu-button" label="Test bug 557987" + onclick="eventReceived('click');" + oncommand="eventReceived('command');"> + <menupopup onpopupshowing="eventReceived('popupshowing'); return false;" /> + </toolbarbutton> + <menulist id="menulist" editable="true" value="Test bug 557987" + onfocus="eventReceived('focus')" /> + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(test); + +// Tests that mouse events are correctly dispatched to <toolbarbutton type="menu-button"/> +function test() { + + disableNonTestMouseEvents(true); + + let button = $("button"); + let rightEdge = button.getBoundingClientRect().width - 2; + let centerX = button.getBoundingClientRect().width / 2; + let centerY = button.getBoundingClientRect().height / 2; + + synthesizeMouse(button, rightEdge, centerY, {}, window); + synthesizeMouse(button, centerX, centerY, {}, window); + + let menulist = $("menulist"); + centerX = menulist.getBoundingClientRect().width / 2; + centerY = menulist.getBoundingClientRect().height / 2; + synthesizeMouse(menulist, centerX, centerY, {}, window); + + synthesizeMouse(document.getElementsByTagName("body")[0], 0, 0, {}, window); + + disableNonTestMouseEvents(false); + SimpleTest.executeSoon(finishTest); + +} + +function finishTest() { + is(eventCount.command, 1, "Correct number of command events received"); + is(eventCount.popupshowing, 1, "Correct number of popupshowing events received"); + is(eventCount.click, 2, "Correct number of click events received"); + is(eventCount.focus, 1, "Correct number of focus events received"); + + SimpleTest.finish(); +} + +let eventCount = { + command: 0, + popupshowing: 0, + click: 0, + focus: 0 +}; + +function eventReceived(eventName) { + eventCount[eventName]++; +} + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug562554.xul b/toolkit/content/tests/chrome/test_bug562554.xul new file mode 100644 index 0000000000..7ee9ef03d6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug562554.xul @@ -0,0 +1,92 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for bug 562554 + --> +<window title="Bug 562554" width="400" height="400" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<xbl:bindings xmlns:xbl="http://www.mozilla.org/xbl"> + <xbl:binding id="menu" display="xul:menu" + extends="chrome://global/content/bindings/button.xml#button-base"> + <xbl:content> + <xbl:children includes="menupopup"/> + <xul:stack> + <xul:button width="100" left="0" top="0" height="30" allowevents="true" + onclick="eventReceived('clickbutton1'); return false;"/> + <xul:button width="100" left="70" top="0" height="30" + onclick="eventReceived('clickbutton2'); return false;"/> + </xul:stack> + </xbl:content> + </xbl:binding> +</xbl:bindings> + + <toolbarbutton type="menu" id="toolbarmenu" height="200" style="-moz-binding: url(#menu);"> + <menupopup id="menupopup" onpopupshowing="eventReceived('popupshowing'); return false;"/> + </toolbarbutton> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(test); + +// Tests that mouse events are correctly dispatched to <toolbarbutton type="menu"/> +function test() { + disableNonTestMouseEvents(true); + nextTest(); +} + +let tests = [ + // Click on the toolbarbutton itself - should call popupshowing + () => synthesizeMouse($("toolbarmenu"), 10, 50, {}, window), + + // Click on button1 which has allowevents="true" - should call clickbutton1 + () => synthesizeMouse($("toolbarmenu"), 10, 15, {}, window), + + // Click on button2 where it intersects with button1 - should call popupshowing + () => synthesizeMouse($("toolbarmenu"), 85, 15, {}, window), + + // Click on button2 outside of intersection - should call popupshowing + () => synthesizeMouse($("toolbarmenu"), 150, 15, {}, window) +]; + +function nextTest() { + if (tests.length) { + let func = tests.shift(); + func(); + SimpleTest.executeSoon(nextTest); + } else { + disableNonTestMouseEvents(false); + SimpleTest.executeSoon(finishTest); + } +} + +function finishTest() { + is(eventCount.clickbutton1, 1, "Correct number of clicks on button 1"); + is(eventCount.clickbutton2, 0, "Correct number of clicks on button 2"); + is(eventCount.popupshowing, 3, "Correct number of popupshowing events received"); + + SimpleTest.finish(); +} + +let eventCount = { + popupshowing: 0, + clickbutton1: 0, + clickbutton2: 0 +}; + +function eventReceived(eventName) { + eventCount[eventName]++; +} + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug570192.xul b/toolkit/content/tests/chrome/test_bug570192.xul new file mode 100644 index 0000000000..09f73e932b --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug570192.xul @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=570192 +--> +<window title="Mozilla Bug 558406" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + <script type="application/javascript" + src="RegisterUnregisterChrome.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570192"> + Mozilla Bug 570192 + </a> + + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script type="application/javascript"> + <![CDATA[ + + addLoadEvent(function() { + try { + var content = document.getElementById("content"); + content.innerHTML = '<textbox newlines="pasteintact" ' + + 'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>'; + var textbox = content.firstChild; + ok(textbox, "created the textbox"); + ok(!textbox.editor, "do we have an editor?"); + } catch (e) { + ok(false, "Got an exception: " + e); + } + SimpleTest.finish(); + }); + SimpleTest.waitForExplicitFinish(); + + ]]> + </script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug585946.xul b/toolkit/content/tests/chrome/test_bug585946.xul new file mode 100644 index 0000000000..738e46b1b1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug585946.xul @@ -0,0 +1,51 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Toolbar" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="startTest();"> + + <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <toolbox> + <toolbarpalette/> + <toolbar id="toolbar" defaultset="node1,node2"> + <toolbarbutton id="node1" label="node1" removable="true"/> + <toolbarbutton id="node2" label="node2" removable="true"/> + </toolbar> + </toolbox> + + <!-- test resuls are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function startTest() { + var toolbar = $("toolbar"); + + var splitter = document.createElement("splitter"); + splitter.setAttribute("id", "dynsplitter"); + splitter.setAttribute("skipintoolbarset", "true"); + + toolbar.insertBefore(splitter, $("node2")); + + function checkPos() { + is($("dynsplitter").previousSibling, $("node1")); + is($("dynsplitter").nextSibling, $("node2")); + } + + checkPos(); + toolbar.style.MozBinding = "url(chrome://global/content/bindings/toolbar.xml#toolbar-drag)"; + toolbar.clientTop; // style flush + checkPos(); + + SimpleTest.finish(); +} + + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug624329.xul b/toolkit/content/tests/chrome/test_bug624329.xul new file mode 100644 index 0000000000..893b386870 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug624329.xul @@ -0,0 +1,160 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=624329 +--> +<window title="Mozilla Bug 624329 context menu position" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + onload="openTestWindow()"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=624329" + target="_blank">Mozilla Bug 624329</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 624329 **/ + +SimpleTest.waitForExplicitFinish(); + +var win; +var timeoutID; +var menu; + +function openTestWindow() { + win = open("bug624329_window.xul", "_blank", "width=300,resizable=yes,chrome"); + // Close our window if the test times out so that it doesn't interfere + // with later tests. + timeoutID = setTimeout(function () { + ok(false, "Test timed out."); + // Provide some time for a screenshot + setTimeout(finish, 1000); + }, 20000); +} + +function listenOnce(event, callback) { + win.addEventListener(event, function listener() { + win.removeEventListener(event, listener, false); + callback(); + }, false); +} + +function childFocused() { + // maximizing the window is a simple way to ensure that the menu is near + // the right edge of the screen. + + listenOnce("resize", childResized); + win.maximize(); +} + +function childResized() { + const isOSXLion = navigator.userAgent.indexOf("Mac OS X 10.7") != -1; + const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1; + const isOSXMavericks = navigator.userAgent.indexOf("Mac OS X 10.9") != -1; + const isOSXYosemite = navigator.userAgent.indexOf("Mac OS X 10.10") != -1; + if (isOSXLion || isOSXMtnLion || isOSXMavericks || isOSXYosemite) { + todo_is(win.windowState, win.STATE_MAXIMIZED, + "A resize before being maximized breaks this test on 10.7 and 10.8 and 10.9 and 10.10"); + finish(); + return; + } + + is(win.windowState, win.STATE_MAXIMIZED, + "window should be maximized"); + + isnot(win.innerWidth, 300, + "window inner width should have changed"); + + openContextMenu(); +} + +function openContextMenu() { + var mouseX = win.innerWidth - 10; + var mouseY = 10; + + menu = win.document.getElementById("menu"); + var screenX = menu.boxObject.screenX; + var screenY = menu.boxObject.screenY; + var utils = + win.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + + utils.sendMouseEvent("contextmenu", mouseX, mouseY, 2, 0, 0); + + var interval = setInterval(checkMoved, 200); + function checkMoved() { + if (menu.boxObject.screenX != screenX || + menu.boxObject.screenY != screenY) { + clearInterval(interval); + // Wait further to check that the window does not move again. + setTimeout(checkPosition, 1000); + } + } + + function checkPosition() { + var menubox = menu.boxObject; + var winbox = win.document.documentElement.boxObject; + var platformIsMac = navigator.userAgent.indexOf("Mac") > -1; + + var x = menubox.screenX - winbox.screenX; + var y = menubox.screenY - winbox.screenY; + + if (platformIsMac) + { + // This check is alterered slightly for OSX which adds padding to the top + // and bottom of its context menus. The menu position calculation must + // be changed to allow for the pointer to be outside this padding + // when the menu opens. + // (Bug 1075089) + ok(y + 6 >= mouseY, + "menu top " + (y + 6) + " should be below click point " + mouseY); + } + else + { + ok(y >= mouseY, + "menu top " + y + " should be below click point " + mouseY); + } + + ok(y <= mouseY + 20, + "menu top " + y + " should not be too far below click point " + mouseY); + + ok(x < mouseX, + "menu left " + x + " should be left of click point " + mouseX); + var right = x + menubox.width; + + if (platformIsMac) { + // Rather than be constrained by the right hand screen edge, OSX menus flip + // horizontally and appear to the left of the mouse pointer + ok(right < mouseX, + "menu right " + right + " should be left of click point " + mouseX); + } + else { + ok(right > mouseX, + "menu right " + right + " should be right of click point " + mouseX); + } + + clearTimeout(timeoutID); + finish(); + } + +} + +function finish() { + if (menu && navigator.platform.indexOf("Win") >= 0) { + todo(false, "Should not have to hide popup before closing its window"); + // This avoids mochitest "Unable to restore focus" errors (bug 670053). + menu.hidePopup(); + } + win.close(); + SimpleTest.finish(); +} + + ]]> + </script> +</window> diff --git a/toolkit/content/tests/chrome/test_bug792324.xul b/toolkit/content/tests/chrome/test_bug792324.xul new file mode 100644 index 0000000000..a6fa425056 --- /dev/null +++ b/toolkit/content/tests/chrome/test_bug792324.xul @@ -0,0 +1,75 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=792324 +--> +<window title="Mozilla Bug 792324" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +<body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=792324">Mozilla Bug 792324</a> + + <p id="display"></p> +<div id="content" style="display: none"> +</div> +</body> + +<panel id="panel-1"> + <button label="just a normal button"/> + <button id="button-1" + accesskey="X" + oncommand="clicked(event)" + label="Button in panel 1" + /> +</panel> + +<panel id="panel-2"> + <button label="just a normal button"/> + <button id="button-2" + accesskey="X" + oncommand="clicked(event)" + label="Button in panel 2" + /> +</panel> + +<script class="testbody" type="application/javascript;version=1.7"><![CDATA[ + +/** Test for Bug 792324 **/ +let after_click; + +function clicked(event) { + after_click(event); +} + +function checkAccessKeyOnPanel(panelid, buttonid, cb) { + let panel = document.getElementById(panelid); + panel.addEventListener("popupshown", function onpopupshown() { + panel.removeEventListener("popupshown", onpopupshown); + panel.firstChild.focus(); + after_click = function(event) { + is(event.target.id, buttonid, "Accesskey was directed to the button '" + buttonid + "'"); + panel.hidePopup(); + cb(); + } + synthesizeKey("X", {}); + }); + panel.openPopup(null, "", 100, 100, false, false); +} + +function test() { + checkAccessKeyOnPanel("panel-1", "button-1", function() { + checkAccessKeyOnPanel("panel-2", "button-2", function() { + SimpleTest.finish(); + }); + }); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(test, window); + +]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_button.xul b/toolkit/content/tests/chrome/test_button.xul new file mode 100644 index 0000000000..fa4e7b0035 --- /dev/null +++ b/toolkit/content/tests/chrome/test_button.xul @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for button + --> +<window title="Button Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<button id="one" label="One" /> +<button id="two" label="Two"/> +<hbox> + <button id="three" label="Three" open="true"/> +</hbox> +<hbox> + <button id="four" type="menu" label="Four"/> + <button id="five" type="panel" label="Five"/> + <button id="six" label="Six"/> +</hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_button() +{ + synthesizeMouseExpectEvent($("one"), 2, 2, {}, $("one"), "command", "button press"); + $("one").focus(); + synthesizeKeyExpectEvent("VK_SPACE", { }, $("one"), "command", "key press"); + $("two").disabled = true; + synthesizeMouseExpectEvent($("two"), 2, 2, {}, $("two"), "!command", "button press command when disabled"); + synthesizeMouseExpectEvent($("two"), 2, 2, {}, $("two"), "click", "button press click when disabled"); + + if (navigator.platform.indexOf("Mac") == -1) { + $("one").focus(); + synthesizeKey("VK_DOWN", { }); + is(document.activeElement, $("three"), "key cursor down on button"); + + synthesizeKey("VK_RIGHT", { }); + is(document.activeElement, $("four"), "key cursor right on button"); + synthesizeKey("VK_DOWN", { }); + is(document.activeElement, $("four"), "key cursor down on menu button"); + $("five").focus(); + synthesizeKey("VK_DOWN", { }); + is(document.activeElement, $("five"), "key cursor down on panel button"); + + $("three").focus(); + synthesizeKey("VK_UP", { }); + is(document.activeElement, $("one"), "key cursor up on button"); + } + + $("two").focus(); + ok(document.activeElement != $("two"), "focus disabled button"); + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(test_button); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_chromemargin.xul b/toolkit/content/tests/chrome/test_chromemargin.xul new file mode 100644 index 0000000000..79c4f7525e --- /dev/null +++ b/toolkit/content/tests/chrome/test_chromemargin.xul @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Custom chrome margin tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> + +// Tests parsing of the chrome margin attrib on a window. + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_chromemargin.xul", "_blank", "chrome,width=600,height=600"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_closemenu_attribute.xul b/toolkit/content/tests/chrome/test_closemenu_attribute.xul new file mode 100644 index 0000000000..c1e93734fc --- /dev/null +++ b/toolkit/content/tests/chrome/test_closemenu_attribute.xul @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu closemenu Attribute Tests" + onload="setTimeout(nextTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<button id="menu" type="menu" label="Menu" onpopuphidden="popupHidden(event)"> + <menupopup id="p1" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="l1" label="One"> + <menupopup id="p2" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="l2" label="Two"> + <menupopup id="p3" onpopupshown="executeMenuItem()"> + <menuitem id="l3" label="Three"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> +</button> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gExpectedId = "p3"; +var gMode = -1; +var gModes = ["", "auto", "single", "none"]; + +function nextTest() +{ + gMode++; + if (gModes[gMode] != "none") + gExpectedId = "p3"; + + if (gMode != 0) + $("l3").setAttribute("closemenu", gModes[gMode]); + if (gModes[gMode] == "none") + $("l2").open = true; + else + $("menu").open = true; +} + +function executeMenuItem() +{ + synthesizeKey("VK_DOWN", { }); + synthesizeKey("VK_RETURN", { }); + // after a couple of seconds, end the test, as the 'none' closemenu value + // should not hide any popups + if (gModes[gMode] == "none") + setTimeout(function() { $("menu").open = false; }, 2000); +} + +function popupHidden(event) +{ + if (gModes[gMode] == "none") { + if (event.target.id == "p1") + SimpleTest.finish() + return; + } + + is(event.target.id, gExpectedId, + "Expected event " + gModes[gMode] + " " + gExpectedId); + + gExpectedId = ""; + if (event.target.id == "p3") { + if (gModes[gMode] == "" || gModes[gMode] == "auto") + gExpectedId = "p2"; + } + else if (event.target.id == "p2") { + if (gModes[gMode] == "" || gModes[gMode] == "auto") + gExpectedId = "p1"; + } + + if (!gExpectedId) + nextTest(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_colorpicker_popup.xul b/toolkit/content/tests/chrome/test_colorpicker_popup.xul new file mode 100644 index 0000000000..3ac84260b2 --- /dev/null +++ b/toolkit/content/tests/chrome/test_colorpicker_popup.xul @@ -0,0 +1,148 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Colorpicker Tests" + onload="setTimeout(runTests, 0);" + onpopupshown="popupShown();" + onpopuphidden="popupHiding();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<colorpicker id="colorpicker-popup" type="button" color="#FF0000" tabindex="1"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var gTestPhase = -1; +var gCp = null; + +SimpleTest.waitForExplicitFinish(); + +function preventDefault(event) { + event.preventDefault(); +} + +function runTests() +{ + gCp = document.getElementById("colorpicker-popup"); + is(gCp.color, "#FF0000", "popup color is initialized"); + is(gCp.tabIndex, 1, "button tabindex is initialized"); + is(gCp.disabled, false, "button is not disabled"); + + document.addEventListener("keypress", preventDefault, false); + + goNext(); +} + +var phases = [ "mouse click", "showPopup", + "key left", "key right", "key up", "key down", "key space" ]; + +function popupShown() +{ + if (gTestPhase >= phases.length) + return; + + var phase = phases[gTestPhase]; + + is(gCp.open, true, phase + " popup shown, open property is true"); + + switch (phase) { + case "mouse click": + synthesizeMouse(gCp, 2, 2, { }); + break; + case "showPopup": + gCp.hidePopup(); + break; + case "key left": + synthesizeKey("VK_LEFT", { }); + synthesizeKeyExpectEvent("VK_RETURN", { }); + is(gCp.color, "#C0C0C0", "key left while open"); + break; + case "key right": + synthesizeKey("VK_RIGHT", { }); + synthesizeKeyExpectEvent("VK_SPACE", { }); + is(gCp.color, "#FF0000", "key right while open"); + break; + case "key up": + synthesizeKey("VK_UP", { }); + synthesizeKeyExpectEvent("VK_RETURN", { }); + is(gCp.color, "#FF6666", "key up while open"); + break; + case "key down": + synthesizeKey("VK_DOWN", { }); + synthesizeKeyExpectEvent("VK_SPACE", { }); + is(gCp.color, "#FF0000", "key down while open"); + break; + default: + synthesizeMouse(gCp, 2, 2, { }); +// this breaks on the Mac, so disable for now +// synthesizeKey("VK_ESCAPE", { }); + break; + } +} + +function popupHiding() +{ + var phase = phases[gTestPhase]; + if (phase == "showPopup") + phase = "hidePopup"; + if (phase == "key left") + phase = "escape"; + is(gCp.open, false, phase + " popup hidden, open property is false"); + + goNext(); +} + +function goNext() +{ + gTestPhase++; + if (gTestPhase >= phases.length) { + document.removeEventListener("keypress", preventDefault, false); + SimpleTest.finish(); + return; + } + + gCp.focus(); + + var phase = phases[gTestPhase]; + switch (phase) { + case "mouse click": + synthesizeMouse(gCp, 2, 2, { }); + break; + case "showPopup": + gCp.showPopup(); + break; + case "key left": + synthesizeKey("VK_LEFT", { }); + break; + case "key right": + synthesizeKey("VK_RIGHT", { }); + break; + case "key down": + synthesizeKey("VK_UP", { }); + break; + case "key up": + synthesizeKey("VK_DOWN", { }); + break; + case "key space": + synthesizeKey("VK_SPACE", { }); + break; + } +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_contextmenu_list.xul b/toolkit/content/tests/chrome/test_contextmenu_list.xul new file mode 100644 index 0000000000..157831a581 --- /dev/null +++ b/toolkit/content/tests/chrome/test_contextmenu_list.xul @@ -0,0 +1,288 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Context Menu on List Tests" + onload="setTimeout(startTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<spacer height="5"/> + +<hbox style="padding-left: 10px;"> + <spacer width="5"/> + <richlistbox id="list" context="themenu" style="padding: 0;" oncontextmenu="checkContextMenu(event)"> + <richlistitem id="item1" style="padding-top: 3px; margin: 0;"><button label="One"/></richlistitem> + <richlistitem id="item2" height="22"><checkbox label="Checkbox"/></richlistitem> + <richlistitem id="item3"><button label="Three"/></richlistitem> + <richlistitem id="item4"><checkbox label="Four"/></richlistitem> + </richlistbox> + + <tree id="tree" rows="5" flex="1" context="themenu" style="-moz-appearance: none; border: 0"> + <treecols> + <treecol label="Name" flex="1"/> + <splitter class="tree-splitter"/> + <treecol label="Moons"/> + </treecols> + <treechildren id="treechildren"> + <treeitem> + <treerow> + <treecell label="Mercury"/> + <treecell label="0"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Venus"/> + <treecell label="0"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Earth"/> + <treecell label="1"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mars"/> + <treecell label="2"/> + </treerow> + </treeitem> + </treechildren> + </tree> + + <menu id="menu" label="Menu"> + <menupopup id="menupopup" onpopupshown="menuTests()" onpopuphidden="nextTest()" + oncontextmenu="checkContextMenuForMenu(event)"> + <menuitem id="menu1" label="Menu 1"/> + <menuitem id="menu2" label="Menu 2"/> + <menuitem id="menu3" label="Menu 3"/> + </menupopup> + </menu> + +</hbox> + +<menupopup id="themenu" onpopupshowing="if (gTestId == -1) event.preventDefault()" + onpopupshown="checkPopup()" onpopuphidden="setTimeout(nextTest, 0);"> + <menuitem label="Item"/> +</menupopup> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gTestId = -1; +var gTestElement = "list"; +var gSelectionStep = 0; +var gContextMenuFired = false; + +function startTest() +{ + // first, check if the richlistbox selection changes on a contextmenu mouse event + var element = $("list"); + synthesizeMouse(element.getItemAtIndex(3), 7, 1, { type : "mousedown", button: 2, ctrlKey: true }); + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + + gSelectionStep++; + synthesizeMouse(element.getItemAtIndex(1), 7, 1, { type : "mousedown", button: 2, ctrlKey: true, shiftKey: true }); + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + + gSelectionStep++; + synthesizeMouse(element.getItemAtIndex(1), 7, 1, { type : "mousedown", button: 2 }); + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + + $("menu").open = true; +} + +function menuTests() +{ + gSelectionStep = 0; + var element = $("menu"); + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + is(gContextMenuFired, true, "context menu fired when menu open"); + + gSelectionStep = 1; + $("menu").boxObject.activeChild = $("menu2"); + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + + $("menu").open = false; +} + +function nextTest() +{ + gTestId++; + if (gTestId > 2) { + if (gTestElement == "list") { + gTestElement = "tree"; + gTestId = 0; + } + else { + SimpleTest.finish(); + return; + } + } + var element = $(gTestElement); + element.focus(); + if (gTestId == 0) { + if (gTestElement == "list") + element.selectedIndex = 2; + element.currentIndex = 2; + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + } + else if (gTestId == 1) { + synthesizeMouse(element, 7, 4, { type : "contextmenu", button: 2 }); + } + else { + element.currentIndex = -1; + element.selectedIndex = -1; + synthesizeMouse(element, 0, 0, { type : "contextmenu", button: 0 }); + } +} + +// This is nasty so I'd better explain what's going on. +// The basic problem is that the synthetic mouse coordinate generated +// by DOMWindowUtils.sendMouseEvent and also the synthetic mouse coordinate +// generated internally when contextmenu events are redirected to the focused +// element are rounded to the nearest device pixel. But this rounding is done +// while the coordinates are relative to the nearest widget. When this test +// is run in the mochitest harness, the nearest widget is the main mochitest +// window, and our document can have a fractional position within that +// mochitest window. So when we round coordinates for comparison in this +// test, we need to do so very carefully, especially if the target element +// also has a fractional position within our document. +// +// For example, if the y-offset of our containing IFRAME is 100.4px, +// and the offset of our expected point is 10.3px in our document, the actual +// mouse event is dispatched to round(110.7) == 111px. This comes back +// with a clientY of round(111 - 100.4) == round(10.6) == 11. This is not +// equal to round(10.3) as you might expect. + +function isRoundedX(a, b, msg) +{ + is(Math.round(a + mozInnerScreenX), Math.round(b + mozInnerScreenX), msg); +} + +function isRoundedY(a, b, msg) +{ + is(Math.round(a + mozInnerScreenY), Math.round(b + mozInnerScreenY), msg); +} + +function checkContextMenu(event) +{ + var rect = $(gTestElement).getBoundingClientRect(); + + var frombase = (gTestId == -1 || gTestId == 1); + if (!frombase) + rect = event.originalTarget.getBoundingClientRect(); + var left = frombase ? rect.left + 7 : rect.left; + var top = frombase ? rect.top + 4 : rect.bottom; + + isRoundedX(event.clientX, left, gTestElement + " clientX " + gSelectionStep + " " + gTestId + "," + frombase); + isRoundedY(event.clientY, top, gTestElement + " clientY " + gSelectionStep + " " + gTestId); + ok(event.screenX > left, gTestElement + " screenX " + gSelectionStep + " " + gTestId); + ok(event.screenY > top, gTestElement + " screenY " + gSelectionStep + " " + gTestId); + + // context menu from mouse click + switch (gTestId) { + case -1: + var expected = gSelectionStep == 2 ? 1 : (platformIsMac() ? 3 : 0); + is($(gTestElement).selectedIndex, expected, "index after click " + gSelectionStep); + break; + case 0: + if (gTestElement == "list") + is(event.originalTarget, $("item3"), "list selection target"); + else + is(event.originalTarget, $("treechildren"), "tree selection target"); + break; + case 1: + is(event.originalTarget.id, $("item1").id, "list mouse selection target"); + break; + case 2: + is(event.originalTarget, $("list"), "list no selection target"); + break; + } +} + +function checkContextMenuForMenu(event) +{ + gContextMenuFired = true; + + var popuprect = (gSelectionStep ? $("menu2") : $("menupopup")).getBoundingClientRect(); + is(event.clientX, Math.round(popuprect.left), "menu left " + gSelectionStep); + // the clientY is off by one sometimes on Windows (when loaded in the testing iframe + // but not when loaded separately) so just check for both cases for now + ok(event.clientY == Math.round(popuprect.bottom) || + event.clientY - 1 == Math.round(popuprect.bottom), "menu top " + gSelectionStep); +} + +function checkPopup() +{ + var menurect = $("themenu").getBoundingClientRect(); + + // Context menus are offset by a number of pixels from the mouse click + // which activates them. This is so that they don't appear exactly + // under the mouse which can cause them to be mistakenly dismissed. + // The number of pixels depends on the platform and is defined in + // each platform's nsLookAndFeel + var contextMenuOffsetX = platformIsMac() ? 1 : 2; + var contextMenuOffsetY = platformIsMac() ? -6 : 2; + + if (gTestId == 0) { + if (gTestElement == "list") { + var itemrect = $("item3").getBoundingClientRect(); + isRoundedX(menurect.left, itemrect.left + contextMenuOffsetX, + "list selection keyboard left"); + isRoundedY(menurect.top, itemrect.bottom + contextMenuOffsetY, + "list selection keyboard top"); + } + else { + var tree = $("tree"); + var bodyrect = $("treechildren").getBoundingClientRect(); + isRoundedX(menurect.left, bodyrect.left + contextMenuOffsetX, + "tree selection keyboard left"); + isRoundedY(menurect.top, bodyrect.top + + tree.treeBoxObject.rowHeight * 3 + contextMenuOffsetY, + "tree selection keyboard top"); + } + } + else if (gTestId == 1) { + // activating a context menu with the mouse from position (7, 4). + var elementrect = $(gTestElement).getBoundingClientRect(); + isRoundedX(menurect.left, elementrect.left + 7 + contextMenuOffsetX, + gTestElement + " mouse left"); + isRoundedY(menurect.top, elementrect.top + 4 + contextMenuOffsetY, + gTestElement + " mouse top"); + } + else { + var elementrect = $(gTestElement).getBoundingClientRect(); + isRoundedX(menurect.left, elementrect.left + contextMenuOffsetX, + gTestElement + " no selection keyboard left"); + isRoundedY(menurect.top, elementrect.bottom + contextMenuOffsetY, + gTestElement + " no selection keyboard top"); + } + + $("themenu").hidePopup(); +} + +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_cursorsnap.xul b/toolkit/content/tests/chrome/test_cursorsnap.xul new file mode 100644 index 0000000000..de153e7040 --- /dev/null +++ b/toolkit/content/tests/chrome/test_cursorsnap.xul @@ -0,0 +1,127 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Cursor snapping test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js" /> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +const kMaxRetryCount = 4; +const kTimeoutTime = [ + 100, 100, 1000, 1000, 5000 +]; + +var gRetryCount; + +var gTestingCount = 0; +var gTestingIndex = -1; +var gDisable = false; +var gHidden = false; + +function canRetryTest() +{ + return gRetryCount <= kMaxRetryCount; +} + +function getTimeoutTime() +{ + return kTimeoutTime[gRetryCount]; +} + +function runNextTest() +{ + gRetryCount = 0; + gTestingIndex++; + runCurrentTest(); +} + +function retryCurrentTest() +{ + ok(canRetryTest(), "retry the current test..."); + gRetryCount++; + runCurrentTest(); +} + +function runCurrentTest() +{ + var position = "top=" + gTestingCount + ",left=" + gTestingCount + ","; + gTestingCount++; + switch (gTestingIndex) { + case 0: + gDisable = false; + gHidden = false; + window.open("window_cursorsnap_dialog.xul", "_blank", + position + "chrome,width=100,height=100"); + break; + case 1: + gDisable = true; + gHidden = false; + window.open("window_cursorsnap_dialog.xul", "_blank", + position + "chrome,width=100,height=100"); + break; + case 2: + gDisable = false; + gHidden = true; + window.open("window_cursorsnap_dialog.xul", "_blank", + position + "chrome,width=100,height=100"); + break; + case 3: + gDisable = false; + gHidden = false; + window.open("window_cursorsnap_wizard.xul", "_blank", + position + "chrome,width=100,height=100"); + break; + case 4: + gDisable = true; + gHidden = false; + window.open("window_cursorsnap_wizard.xul", "_blank", + position + "chrome,width=100,height=100"); + break; + case 5: + gDisable = false; + gHidden = true; + window.open("window_cursorsnap_wizard.xul", "_blank", + position + "chrome,width=100,height=100"); + break; + default: + SetPrefs(false); + SimpleTest.finish(); + return; + } +} + +function SetPrefs(aSet) +{ + var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]. + getService(Components.interfaces.nsIPrefBranch); + const kPrefName = "ui.cursor_snapping.always_enabled"; + if (aSet) { + prefSvc.setBoolPref(kPrefName, true); + } else if (prefSvc.prefHasUserValue(kPrefName)) { + prefSvc.clearUserPref(kPrefName); + } +} + +SetPrefs(true); +runNextTest(); + +]]> +</script> +</window> diff --git a/toolkit/content/tests/chrome/test_datepicker.xul b/toolkit/content/tests/chrome/test_datepicker.xul new file mode 100644 index 0000000000..e7a61f43bc --- /dev/null +++ b/toolkit/content/tests/chrome/test_datepicker.xul @@ -0,0 +1,415 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for datepicker + --> +<window title="datepicker" width="500" height="600" + onload="setTimeout(testtag_datepickers, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<hbox onpopupshown="testtag_datepicker_UI_popup()" + onpopuphidden="testtag_finish()"> +<datepicker id="datepicker"/> +<datepicker id="datepicker-popup" type="popup"/> +<hbox onDOMMouseScroll="mouseScrolled = event.defaultPrevented;"> + <datepicker id="datepicker-grid" type="grid" value="2007-04-21"/> +</hbox> +</hbox> + +<!-- Test-only key bindings, but must not conflict with the application. --> +<keyset id="mainKeyset"> + <key id="key_alt_z" key="Z" oncommand="return" modifiers="alt"/> + <key id="key_ctrl_q" key="Q" oncommand="return" modifiers="control"/> + <key id="key_meta_e" key="E" oncommand="return" modifiers="meta"/> +</keyset> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +var mouseScrolled = false; + +SimpleTest.waitForExplicitFinish(); + +function testtag_datepickers() +{ + var dppopup = document.getElementById("datepicker-popup"); + testtag_datepicker(document.getElementById("datepicker"), "", "datepicker"); + testtag_datepicker(dppopup, "popup", "datepicker popup"); + + var gridpicker = document.getElementById("datepicker-grid"); + is(gridpicker.monthField.selectedIndex, "3", "datepicker grid correct month is initially selected"); + testtag_datepicker(gridpicker, "grid", "datepicker grid"); + dppopup.open = true; +} + +function testtag_finish() +{ + ok(!document.getElementById("datepicker-popup").open, "datepicker popup open false again"); + + var dpgrid = document.getElementById("datepicker-grid"); + synthesizeWheel(dpgrid, 5, 5, { deltaY: 10.0, + deltaMode: WheelEvent.DOM_DELTA_LINE }); + is(mouseScrolled, true, "mouse scrolled"); + is(dpgrid.displayedMonth, 2, "mouse scroll changed month"); + + SimpleTest.finish(); +} + +function testtag_datepicker(dp, type, testid) +{ + testid += " "; + + var today = new Date(); + var tyear = today.getFullYear(); + var tmonth = today.getMonth(); + var tdate = today.getDate(); + + // testtag_comparedate(dp, testid + "initial", tyear, tmonth, tdate); + + // check that setting the value property works + dp.value = testtag_getdatestring(tyear, tmonth, tdate); + testtag_comparedate(dp, testid + "set value", tyear, tmonth, tdate); + + // check that setting the dateValue property works + dp.dateValue = today; + testtag_comparedate(dp, testid + "set dateValue", tyear, tmonth, tdate); + ok(dp.value !== today, testid + " set dateValue different date"); + + ok(!dp.readOnly, testid + "readOnly"); + dp.readOnly = true; + ok(dp.readOnly, testid + "set readOnly"); + dp.readOnly = false; + ok(!dp.readOnly, testid + "clear readOnly"); + + var setDateField = function(field, value, expectException, + expectedYear, expectedMonth, expectedDate) + { + var exh = false; + try { + dp[field] = value; + } catch (ex) { exh = true; } + is(exh, expectException, testid + "set " + field + " " + value); + testtag_comparedate(dp, testid + "set " + field + " " + value, + expectedYear, expectedMonth, expectedDate); + } + + // check the value property + setDateField("value", "2003-1-27", false, 2003, 0, 27); + setDateField("value", "2002-11-8", false, 2002, 10, 8); + setDateField("value", "2001-07-02", false, 2001, 6, 2); + setDateField("value", "2002-10-25", false, 2002, 9, 25); + + // check that the year, month and date fields can be set properly + setDateField("year", 2002, false, 2002, 9, 25); + setDateField("year", 0, true, 2002, 9, 25); + + setDateField("month", 6, false, 2002, 6, 25); + setDateField("month", 9, false, 2002, 9, 25); + setDateField("month", 10, false, 2002, 10, 25); + setDateField("month", -1, true, 2002, 10, 25); + setDateField("month", 12, true, 2002, 10, 25); + + setDateField("date", 9, false, 2002, 10, 9); + setDateField("date", 10, false, 2002, 10, 10); + setDateField("date", 15, false, 2002, 10, 15); + setDateField("date", 0, true, 2002, 10, 15); + setDateField("date", 32, true, 2002, 10, 15); + + // check leap year handling + setDateField("value", "1600-2-29", false, 1600, 1, 29); + setDateField("value", "2000-2-29", false, 2000, 1, 29); + setDateField("value", "2003-2-29", false, 2003, 2, 1); + setDateField("value", "2004-2-29", false, 2004, 1, 29); + setDateField("value", "2100-2-29", false, 2100, 2, 1); + + // check invalid values for the value and dateValue properties + dp.value = "2002-07-15"; + setDateField("value", "", true, 2002, 6, 15); + setDateField("value", "2-2", true, 2002, 6, 15); + setDateField("value", "2000-5-6-6", true, 2002, 6, 15); + setDateField("value", "2000-a-19", true, 2002, 6, 15); + setDateField("dateValue", "none", true, 2002, 6, 15); + + // grid and popup types can display a different month than the current one + var isGridOrPopup = (type == "grid" || type == "popup"); + dp.displayedMonth = 3; + testtag_comparedate(dp, testid + "set displayedMonth", + 2002, isGridOrPopup ? 6 : 3, 15, 3); + + dp.displayedYear = 2009; + testtag_comparedate(dp, testid + "set displayedYear", + isGridOrPopup ? 2002 : 2009, isGridOrPopup ? 6 : 3, 15, 3, 2009); + + if (isGridOrPopup) { + dp.value = "2008-02-29"; + dp.displayedYear = 2009; + is(dp.displayedMonth, 1, "set displayedYear during leap year"); + } + + is(dp.open, false, testid + "open false"); + if (type != "popup") { + dp.open = true; + ok(!dp.open, testid + "open still false"); + } + + // check the fields + if (type != "grid") { + ok(dp.yearField instanceof HTMLInputElement, testid + "yearField"); + ok(dp.monthField instanceof HTMLInputElement, testid + "monthField"); + ok(dp.dateField instanceof HTMLInputElement, testid + "dateField"); + + testtag_datepicker_UI_fields(dp, testid); + + dp.readOnly = true; + + // check that keyboard usage doesn't change the value when the datepicker + // is read only + testtag_datepicker_UI_key(dp, testid + "readonly ", "2003-01-29", + dp.yearField, 2003, 0, 29, 2003, 0, 29); + testtag_datepicker_UI_key(dp, testid + "readonly ", "2003-04-29", + dp.monthField, 2003, 3, 29, 2003, 3, 29); + testtag_datepicker_UI_key(dp, testid + "readonly ", "2003-06-15", + dp.dateField, 2003, 5, 15, 2003, 5, 15); + + dp.readOnly = false; + } + else { + testtag_datepicker_UI_grid(dp, "grid", testid); + } +} + +function testtag_datepicker_UI_fields(dp, testid) +{ + testid += "UI"; + dp.focus(); + + // test adjusting the date with the up and down keys + testtag_datepicker_UI_key(dp, testid, "2003-01-29", dp.yearField, 2004, 0, 29, 2003, 0, 29); + testtag_datepicker_UI_key(dp, testid, "1600-02-29", dp.yearField, 1601, 1, 28, 1600, 1, 28); + testtag_datepicker_UI_key(dp, testid, "2000-02-29", dp.yearField, 2001, 1, 28, 2000, 1, 28); + testtag_datepicker_UI_key(dp, testid, "2004-02-29", dp.yearField, 2005, 1, 28, 2004, 1, 28); + + testtag_datepicker_UI_key(dp, testid, "2003-04-29", dp.monthField, 2003, 4, 29, 2003, 3, 29); + testtag_datepicker_UI_key(dp, testid, "2003-01-15", dp.monthField, 2003, 1, 15, 2003, 0, 15); + testtag_datepicker_UI_key(dp, testid, "2003-12-29", dp.monthField, 2003, 0, 29, 2003, 11, 29); + testtag_datepicker_UI_key(dp, testid, "2003-03-31", dp.monthField, 2003, 3, 30, 2003, 2, 30); + + testtag_datepicker_UI_key(dp, testid, "2003-06-15", dp.dateField, 2003, 5, 16, 2003, 5, 15); + testtag_datepicker_UI_key(dp, testid, "2003-06-01", dp.dateField, 2003, 5, 2, 2003, 5, 1); + testtag_datepicker_UI_key(dp, testid, "2003-06-30", dp.dateField, 2003, 5, 1, 2003, 5, 30); + testtag_datepicker_UI_key(dp, testid, "1600-02-28", dp.dateField, 1600, 1, 29, 1600, 1, 28); + testtag_datepicker_UI_key(dp, testid, "2000-02-28", dp.dateField, 2000, 1, 29, 2000, 1, 28); + testtag_datepicker_UI_key(dp, testid, "2003-02-28", dp.dateField, 2003, 1, 1, 2003, 1, 28); + testtag_datepicker_UI_key(dp, testid, "2004-02-28", dp.dateField, 2004, 1, 29, 2004, 1, 28); + testtag_datepicker_UI_key(dp, testid, "2100-02-28", dp.dateField, 2100, 1, 1, 2100, 1, 28); + + synthesizeKeyExpectEvent('Z', { altKey: true }, $("key_alt_z"), "command", testid + " alt shortcut"); + synthesizeKeyExpectEvent('Q', { ctrlKey: true }, $("key_ctrl_q"), "command", testid + " ctrl shortcut"); + synthesizeKeyExpectEvent('E', { metaKey: true }, $("key_meta_e"), "command", testid + " meta shortcut"); +} + +function testtag_datepicker_UI_grid(dp, type, testid) +{ + testid += "UI "; + + // check that pressing the cursor keys moves the date properly. For grid + // types, focus the grid first. For popup types, the grid should be focused + // automatically when opening the popup. + var ktarget = dp; + if (type == "grid") + dp.focus(); + else + ktarget = dp.attachedControl; + + dp.value = "2003-02-22"; + + synthesizeKeyExpectEvent("VK_LEFT", { }, ktarget, "change", testid + "key left"); + is(dp.value, "2003-02-21", testid + "key left"); + + synthesizeKeyExpectEvent("VK_RIGHT", { }, ktarget, "change", testid + "key right"); + is(dp.value, "2003-02-22", testid + "key right"); + synthesizeKeyExpectEvent("VK_RIGHT", { }, ktarget, "change", testid + "key right next week"); + is(dp.value, "2003-02-23", testid + "key right next week"); + synthesizeKeyExpectEvent("VK_LEFT", { }, ktarget, "change", testid + "key left previous week"); + is(dp.value, "2003-02-22", testid + "key left previous week"); + + synthesizeKeyExpectEvent("VK_UP", { }, ktarget, "change", testid + "key up"); + is(dp.value, "2003-02-15", testid + "key up"); + synthesizeKeyExpectEvent("VK_DOWN", { }, ktarget, "change", testid + "key down"); + is(dp.value, "2003-02-22", testid + "key down"); + synthesizeKeyExpectEvent("VK_DOWN", { }, ktarget, "change"); + is(dp.value, "2003-03-01", testid + "key down next month", testid + "key down next month"); + synthesizeKeyExpectEvent("VK_UP", { }, ktarget, "change"); + is(dp.value, "2003-02-22", testid + "key up previous month", testid + "key up previous month"); + + // the displayed month may be changed with the page up and page down keys, + // however this only changes the displayed month, not the current value. + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", testid + "key page down"); + is(dp.value, "2003-02-22", testid + "key page down"); + + // the monthchange event is fired when the displayed month is changed + synthesizeKeyExpectEvent("VK_UP", { }, ktarget, "monthchange", testid + "key up after month change"); + is(dp.value, "2003-02-15", testid + "key up after month change"); + + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", testid + "key page up"); + is(dp.value, "2003-02-15", testid + "key page up"); + + // check handling at the start and end of the month + dp.value = "2010-10-01"; + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", testid + "key page up 2010-10-01"); + is(dp.displayedMonth, 8, testid + "key page up 2010-10-01 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-10-01 displayedYear"); + + dp.value = "2010-10-01"; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", testid + "key page down 2010-10-01"); + is(dp.displayedMonth, 10, testid + "key page down 2010-10-01 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page down 2010-10-01 displayedYear"); + + dp.value = "2010-10-31"; + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", testid + "key page up 2010-10-31"); + is(dp.displayedMonth, 8, testid + "key page up 2010-10-31 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-10-01 displayedYear"); + dp.value = "2010-10-31"; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", testid + "key page down 2010-10-31"); + is(dp.displayedMonth, 10, testid + "key page down 2010-10-31 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-10-31 displayedYear"); + + // check handling at the end of february + dp.value = "2010-03-31"; + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", testid + "key page up 2010-03-31"); + is(dp.displayedMonth, 1, testid + "key page up 2010-03-31 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-03-31 displayedYear"); + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", testid + "key page up 2010-02-28"); + is(dp.displayedMonth, 0, testid + "key page up 2010-02-28 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-02-28 displayedYear"); + + dp.value = "2010-01-31"; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", testid + "key page down 2010-01-31"); + is(dp.displayedMonth, 1, testid + "key page down 2010-01-31 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-01-31 displayedYear"); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", testid + "key page down 2010-02-28"); + is(dp.displayedMonth, 2, testid + "key page down 2010-02-28 displayedMonth"); + is(dp.displayedYear, 2010, testid + "key page up 2010-02-28 displayedYear"); + + // check handling at the end of february during a leap year + dp.value = "2008-01-31"; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", testid + "key page down 2008-01-31"); + is(dp.displayedMonth, 1, testid + "key page down 2008-01-31 displayedMonth"); + is(dp.displayedYear, 2008, testid + "key page up 2008-01-31 displayedYear"); + dp.value = "2008-03-31"; + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", testid + "key page up 2008-03-31"); + is(dp.displayedMonth, 1, testid + "key page up 2008-03-31 displayedMonth"); + is(dp.displayedYear, 2008, testid + "key page up 2008-03-31 displayedYear"); + + // the value of a read only datepicker cannot be changed + dp.value = "2003-02-15"; + + dp.readOnly = true; + synthesizeKeyExpectEvent("VK_LEFT", { }, ktarget, "!change", testid + "key left read only"); + is(dp.value, "2003-02-15", testid + "key left read only"); + synthesizeKeyExpectEvent("VK_RIGHT", { }, ktarget, "!change", testid + "key right read only"); + is(dp.value, "2003-02-15", testid + "key right read only"); + synthesizeKeyExpectEvent("VK_DOWN", { }, ktarget, "!change", testid + "key down read only"); + is(dp.value, "2003-02-15", testid + "key down read only"); + synthesizeKeyExpectEvent("VK_UP", { }, ktarget, "!change", testid + "key up read only"); + is(dp.value, "2003-02-15", testid + "key up read only"); + + // month can still be changed even when readonly + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "monthchange", + testid + "key page up read only"); + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "monthchange", + testid + "key page down read only"); + + dp.readOnly = false; + synthesizeKeyExpectEvent("VK_LEFT", { }, ktarget, "change", testid + "key left changeable again"); + is(dp.value, "2003-02-14", testid + "key left changeable again"); + + // the value of a disabled datepicker cannot be changed + dp.disabled = true; + synthesizeKeyExpectEvent("VK_LEFT", { }, ktarget, "!change", testid + "key left disabled"); + is(dp.value, "2003-02-14", testid + "key left disabled"); + synthesizeKeyExpectEvent("VK_RIGHT", { }, ktarget, "!change", testid + "key right disabled"); + is(dp.value, "2003-02-14", testid + "key right disabled"); + synthesizeKeyExpectEvent("VK_DOWN", { }, ktarget, "!change", testid + "key down disabled"); + is(dp.value, "2003-02-14", testid + "key down disabled"); + synthesizeKeyExpectEvent("VK_UP", { }, ktarget, "!change", testid + "key up disabled"); + is(dp.value, "2003-02-14", testid + "key up disabled"); + + // month cannot be changed even when disabled + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, ktarget, "!monthchange", + testid + "key page down disabled"); + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, ktarget, "!monthchange", + testid + "key page up disabled"); + + dp.disabled = false; + synthesizeKeyExpectEvent("VK_RIGHT", { }, ktarget, "change", testid + "key right enabled again"); + is(dp.value, "2003-02-15", testid + "key right enabled again"); +} + +function testtag_datepicker_UI_popup() +{ + var dppopup = document.getElementById("datepicker-popup"); + is(dppopup.open, true, "datepicker popup after open"); + testtag_datepicker_UI_grid(dppopup, "popup", "datepicker popup "); + dppopup.open = false; +} + +function testtag_datepicker_UI_key(dp, testid, value, field, + uyear, umonth, udate, + dyear, dmonth, ddate) +{ + dp.value = value; + field.focus(); + + synthesizeKey("VK_UP", { }); + testtag_comparedate(dp, testid + " " + value + " key up", uyear, umonth, udate); + + synthesizeKey("VK_DOWN", { }); + testtag_comparedate(dp, testid + " " + value + " key down", dyear, dmonth, ddate); +} + +function testtag_getdatestring(year, month, date) +{ + month = (month < 9) ? ("0" + ++month) : month + 1; + if (date < 10) + date = "0" + date; + return year + "-" + month + "-" + date; +} + +function testtag_comparedate(dp, testid, year, month, date, displayedMonth, displayedYear) +{ + is(dp.value, testtag_getdatestring(year, month, date), testid + " value"); + if (testid.indexOf("initial") == -1) + is(dp.getAttribute("value"), + testtag_getdatestring(year, month, date), + testid + " value attribute"); + + var dateValue = dp.dateValue; + ok(dateValue.getFullYear() == year && + dateValue.getMonth() == month && + dateValue.getDate() == date, + testid + " dateValue"); + + is(dp.year, year, testid + " year"); + is(dp.month, month, testid + " month"); + is(dp.displayedMonth, displayedMonth ? displayedMonth : month, testid + " displayedMonth"); + is(dp.displayedYear, displayedYear ? displayedYear : year, testid + " displayedYear"); + is(dp.date, date, testid + " date"); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_deck.xul b/toolkit/content/tests/chrome/test_deck.xul new file mode 100644 index 0000000000..25c59c38a9 --- /dev/null +++ b/toolkit/content/tests/chrome/test_deck.xul @@ -0,0 +1,133 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for deck + --> +<window title="Deck Test" + onload="setTimeout(run_tests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<deck id="deck1" style="padding-top: 5px; padding-bottom: 12px;"> + <button id="d1b1" label="Button One"/> + <button id="d1b2" label="Button Two is larger" height="80" style="margin: 1px;"/> +</deck> +<deck id="deck2" selectedIndex="1"> + <button id="d2b1" label="Button One"/> + <button id="d2b2" label="Button Two"/> +</deck> +<deck id="deck3" selectedIndex="1"> + <button id="d3b1" label="Remove me"/> + <button id="d3b2" label="Keep me selected"/> +</deck> +<deck id="deck4" selectedIndex="5"> + <button id="d4b1" label="Remove me"/> + <button id="d4b2" label="Remove me"/> + <button id="d4b3" label="Remove me"/> + <button id="d4b4" label="Button 4"/> + <button id="d4b5" label="Button 5"/> + <button id="d4b6" label="Keep me selected"/> + <button id="d4b7" label="Button 7"/> +</deck> +<deck id="deck5" selectedIndex="2"> + <button id="d5b1" label="Button 1"/> + <button id="d5b2" label="Button 2"/> + <button id="d5b3" label="Keep me selected"/> + <button id="d5b4" label="Remove me"/> + <button id="d5b5" label="Remove me"/> + <button id="d5b6" label="Remove me"/> +</deck> + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function run_tests() { + test_deck(); + test_deck_child_removal(); + SimpleTest.finish(); +} + +function test_deck() +{ + var deck = $("deck1"); + ok(deck.selectedIndex === '0', "deck one selectedIndex"); + // this size is the button height, 80, plus the button padding of 1px on each side, + // plus the deck's 5px top padding and the 12px bottom padding. + var rect = deck.getBoundingClientRect(); + is(Math.round(rect.bottom) - Math.round(rect.top), 99, "deck size of largest child"); + synthesizeMouseExpectEvent(deck, 12, 12, { }, $("d1b1"), "click", "mouse on deck one"); + + // change the selected page of the deck and ensure that the mouse click goes + // to the button on that page + deck.selectedIndex = 1; + ok(deck.selectedIndex === '1', "deck one selectedIndex after change"); + synthesizeMouseExpectEvent(deck, 9, 9, { }, $("d1b2"), "click", "mouse on deck one after change"); + + deck = $("deck2"); + ok(deck.selectedIndex === '1', "deck two selectedIndex"); + synthesizeMouseExpectEvent(deck, 9, 9, { }, $("d2b2"), "click", "mouse on deck two"); +} + +function test_deck_child_removal() +{ + // Start with a simple case where we have two child nodes in a deck, with + // the second child (index 1) selected. Removing the first node should + // automatically set the selectedIndex at 0. + let deck = $("deck3"); + let child = $("d3b1"); + is(deck.selectedIndex, "1", "Should have the deck element at index 1 selected"); + + // Remove the child at the 0th index. The deck should automatically + // set the selectedIndex to "0". + child.remove(); + is(deck.selectedIndex, "0", "Should have the deck element at index 0 selected"); + + // Now scale it up by using a deck with 7 child nodes, and remove the + // first three, making sure that the selectedIndex is decremented + // each time. + deck = $("deck4"); + let expectedIndex = 5; + is(deck.selectedIndex, String(expectedIndex), + "Should have the deck element at index " + expectedIndex + " selected"); + + for (let i = 0; i < 3; ++i) { + deck.firstChild.remove(); + expectedIndex--; + is(deck.selectedIndex, String(expectedIndex), + "Should have the deck element at index " + expectedIndex + " selected"); + } + + // Check that removing the currently selected node doesn't change + // behaviour. + deck.childNodes[expectedIndex].remove(); + is(deck.selectedIndex, String(expectedIndex), + "The selectedIndex should not change when removing the node " + + "at the selected index."); + + // Finally, make sure we haven't changed the behaviour when removing + // nodes at indexes greater than the selected node. + deck = $("deck5"); + expectedIndex = 2; + is(deck.selectedIndex, String(expectedIndex), + "Should have the deck element at index " + expectedIndex + " selected"); + + // And then remove all of the nodes, starting from last to first, making + // sure that the selectedIndex does not change. + while (deck.lastChild) { + deck.lastChild.remove(); + is(deck.selectedIndex, String(expectedIndex), + "Should have the deck element at index " + expectedIndex + " selected"); + } +} +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_dialogfocus.xul b/toolkit/content/tests/chrome/test_dialogfocus.xul new file mode 100644 index 0000000000..80474e2b91 --- /dev/null +++ b/toolkit/content/tests/chrome/test_dialogfocus.xul @@ -0,0 +1,102 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<button id="test" label="Test"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestCompleteLog(); + +var expected = [ "one", "_extra2", "tab", "one", "tabbutton2", "tabbutton", "two", "textbox-yes", "one" ]; +// non-Mac will always focus the default button if any of the dialog buttons +// would be focused +if (navigator.platform.indexOf("Mac") == -1) + expected[1] = "_accept"; + +var step = 0; +var fullKeyboardAccess = false; + +function startTest() +{ + var testButton = document.getElementById("test"); + synthesizeKey("VK_TAB", { }); + fullKeyboardAccess = (document.activeElement == testButton); + info("We " + (fullKeyboardAccess ? "have" : "don't have") + " full keyboard access"); + runTest(); +} + +function runTest() +{ + step++; + info("runTest(), step = " + step + ", expected = " + expected[step - 1]); + if (step > expected.length || (!fullKeyboardAccess && step == 2)) { + info("finishing"); + SimpleTest.finish(); + return; + } + + var expectedFocus = expected[step - 1]; + var win = window.openDialog("dialog_dialogfocus.xul", "_new", "chrome,dialog", step); + + function checkDialogFocus(event) + { + info("checkDialogFocus()"); + // if full keyboard access is not on, just skip the tests + var match = false; + if (fullKeyboardAccess) { + if (!(event.target instanceof Element)) { + info("target not an Element"); + return; + } + + if (expectedFocus == "textbox-yes") + match = (win.document.activeElement == win.document.getElementById(expectedFocus).inputField); + else if (expectedFocus[0] == "_") + match = (win.document.activeElement.dlgType == expectedFocus.substring(1)); + else + match = (win.document.activeElement.id == expectedFocus); + info("match = " + match); + if (!match) + return; + } + else { + match = (win.document.activeElement == win.document.documentElement); + info("match = " + match); + } + + win.removeEventListener("focus", checkDialogFocus, true); + ok(match, "focus step " + step); + + win.close(); + SimpleTest.waitForFocus(runTest, window); + } + + win.addEventListener("focus", checkDialogFocus, true); +} + +SimpleTest.waitForFocus(startTest, window); + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_findbar.xul b/toolkit/content/tests/chrome/test_findbar.xul new file mode 100644 index 0000000000..9cbe73c475 --- /dev/null +++ b/toolkit/content/tests/chrome/test_findbar.xul @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=257061 +https://bugzilla.mozilla.org/show_bug.cgi?id=288254 +--> +<window title="Mozilla Bug 257061 and Bug 288254" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=257061">Mozilla Bug 257061</a> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=288254">Mozilla Bug 288254</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 257061 and Bug 288254 **/ +SimpleTest.waitForExplicitFinish(); + +// Since bug 978861, this pref is set to `false` on OSX. For this test, we'll +// set it `true` to disable the find clipboard on OSX, which interferes with +// our tests. +SpecialPowers.pushPrefEnv({ + set: [["accessibility.typeaheadfind.prefillwithselection", true]] +}, () => { + window.open("findbar_window.xul", "findbartest", "chrome,width=600,height=600"); +}); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_findbar_entireword.xul b/toolkit/content/tests/chrome/test_findbar_entireword.xul new file mode 100644 index 0000000000..dc39fe09dd --- /dev/null +++ b/toolkit/content/tests/chrome/test_findbar_entireword.xul @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=269442 +--> +<window title="Mozilla Bug 269442" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=269442"> + Mozilla Bug 269442 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 269442 **/ + SimpleTest.waitForExplicitFinish(); + window.open("findbar_entireword_window.xul", "269442test", + "chrome,width=600,height=600"); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_findbar_events.xul b/toolkit/content/tests/chrome/test_findbar_events.xul new file mode 100644 index 0000000000..d75e5ccb57 --- /dev/null +++ b/toolkit/content/tests/chrome/test_findbar_events.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=793275 +--> +<window title="Mozilla Bug 793275" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=793275"> + Mozilla Bug 793275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + /** Test for Bug 793275 **/ + SimpleTest.waitForExplicitFinish(); + window.open("findbar_events_window.xul", "793275test", + "chrome,width=600,height=600"); + + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_focus_anons.xul b/toolkit/content/tests/chrome/test_focus_anons.xul new file mode 100644 index 0000000000..848590887b --- /dev/null +++ b/toolkit/content/tests/chrome/test_focus_anons.xul @@ -0,0 +1,119 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tests for focus on elements with anonymous focusable children" + onload="SimpleTest.waitForFocus(runTests);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<label accesskey="a" control="menulist"/> +<label accesskey="b" control="textbox"/> +<label accesskey="c" control="scale"/> + +<menulist id="menulist" editable="true"> + <menupopup> + <menuitem label="One"/> + </menupopup> +</menulist> +<textbox id="textbox"/> +<scale id="scale"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gBlurs = 0, gFocuses = 0; +var gExpectedBlur = ""; +var gExpectedFocus = ""; + +function blurOccurred(event) { + gBlurs++; + is(event.originalTarget, gExpectedBlur, "blur " + gBlurs + "," + event.originalTarget.localName); +} + +function focusOccurred(event) { + gFocuses++; + is(event.originalTarget, gExpectedFocus, "focus " + gFocuses + "," + event.originalTarget.localName); +} + +function runTests() +{ + addEventListener("focus", focusOccurred, true); + addEventListener("blur", blurOccurred, true); + + gExpectedBlur = null; + gExpectedFocus = $("menulist").inputField; + $("menulist").focus(); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = $("textbox").inputField; + $("textbox").focus(); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = document.getAnonymousNodes($("scale"))[0]; + $("scale").focus(); + + var accessKeyDetails = (navigator.platform.indexOf("Mac") >= 0) ? + { altKey: true, ctrlKey : true } : + { altKey : true, shiftKey: true }; + + gExpectedBlur = document.getAnonymousNodes($("scale"))[0]; + gExpectedFocus = $("menulist").inputField; + synthesizeKey("a", accessKeyDetails); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = $("textbox").inputField; + synthesizeKey("b", accessKeyDetails); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = document.getAnonymousNodes($("scale"))[0]; + synthesizeKey("c", accessKeyDetails); + + if (navigator.platform.indexOf("Mac") == -1) { + gExpectedBlur = gExpectedFocus; + gExpectedFocus = $("textbox").inputField; + synthesizeKey("VK_TAB", { shiftKey: true }); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = $("menulist").inputField; + synthesizeKey("VK_TAB", { shiftKey: true }); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = $("textbox").inputField; + synthesizeKey("VK_TAB", { }); + + gExpectedBlur = gExpectedFocus; + gExpectedFocus = document.getAnonymousNodes($("scale"))[0]; + synthesizeKey("VK_TAB", { }); + + is(gBlurs, 9, "correct number of blurs"); + is(gFocuses, 10, "correct number of focuses"); + } + else { + is(gBlurs, 5, "correct number of blurs"); + is(gFocuses, 6, "correct number of focuses"); + } + + removeEventListener("focus", focusOccurred, true); + removeEventListener("blur", blurOccurred, true); + + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_hiddenitems.xul b/toolkit/content/tests/chrome/test_hiddenitems.xul new file mode 100644 index 0000000000..7e44852df9 --- /dev/null +++ b/toolkit/content/tests/chrome/test_hiddenitems.xul @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=317422 +--> +<window title="Mozilla Bug 317422" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=317422" + target="_blank">Mozilla Bug 317422</a> + </body> + + <richlistbox id="richlistbox" seltype="multiple"> + <richlistitem id="richlistbox_item1"><label value="Item 1"/></richlistitem> + <richlistitem id="richlistbox_item2"><label value="Item 2"/></richlistitem> + <richlistitem id="richlistbox_item3" hidden="true"><label value="Item 3"/></richlistitem> + <richlistitem id="richlistbox_item4"><label value="Item 4"/></richlistitem> + <richlistitem id="richlistbox_item5" collapsed="true"><label value="Item 5"/></richlistitem> + <richlistitem id="richlistbox_item6"><label value="Item 6"/></richlistitem> + <richlistitem id="richlistbox_item7" hidden="true"><label value="Item 7"/></richlistitem> + </richlistbox> + + <listbox id="listbox" seltype="multiple"> + <listitem id="listbox_item1" label="Item 1"/> + <listitem id="listbox_item2" label="Item 2"/> + <listitem id="listbox_item3" label="Item 3" hidden="true"/> + <listitem id="listbox_item4" label="Item 4"/> + <listitem id="listbox_item5" label="Item 5" collapsed="true"/> + <listitem id="listbox_item6" label="Item 6"/> + <listitem id="listbox_item7" label="Item 7" hidden="true"/> + </listbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 317422 **/ +SimpleTest.waitForExplicitFinish(); + +function testListbox(id) +{ + var listbox = document.getElementById(id); + listbox.focus(); + is(listbox.getRowCount(), 7, id + ": Returned the wrong number of rows"); + is(listbox.getItemAtIndex(2).id, id + "_item3", id + ": Should still return hidden items"); + listbox.selectedIndex = 0; + is(listbox.selectedItem.id, id + "_item1", id + ": First item was not selected"); + sendKey("DOWN"); + is(listbox.selectedItem.id, id + "_item2", id + ": Down didn't move to second item"); + sendKey("DOWN"); + is(listbox.selectedItem.id, id + "_item4", id + ": Down didn't skip hidden item"); + sendKey("DOWN"); + is(listbox.selectedItem.id, id + "_item6", id + ": Down didn't skip collapsed item"); + sendKey("UP"); + is(listbox.selectedItem.id, id + "_item4", id + ": Up didn't skip collapsed item"); + sendKey("UP"); + is(listbox.selectedItem.id, id + "_item2", id + ": Up didn't skip hidden item"); + listbox.selectAll(); + is(listbox.selectedItems.length, 7, id + ": Should have still selected all items"); + listbox.invertSelection(); + is(listbox.selectedItems.length, 0, id + ": Should have unselected all items"); + listbox.selectedIndex = 2; + ok(listbox.selectedItem == listbox.getItemAtIndex(2), id + ": Should have selected the hidden item"); + listbox.selectedIndex = 0; + sendKey("END"); + is(listbox.selectedItem.id, id + "_item6", id + ": Should have moved to the last unhidden item"); + sendMouseEvent({type: 'click'}, id + "_item1"); + ok(listbox.selectedItem == listbox.getItemAtIndex(0), id + ": Should have selected the first item"); + is(listbox.selectedItems.length, 1, id + ": Should only be one selected item"); + sendMouseEvent({type: 'click', shiftKey: true}, id + "_item6"); + is(listbox.selectedItems.length, 4, id + ": Should have selected all visible items"); + listbox.selectedIndex = 0; + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item6", id + ": Page down should go to the last visible item"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item1", id + ": Page up should go to the first visible item"); +} + +window.onload = function runTests() { + testListbox("richlistbox"); + testListbox("listbox"); + SimpleTest.finish(); +}; + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_hiddenpaging.xul b/toolkit/content/tests/chrome/test_hiddenpaging.xul new file mode 100644 index 0000000000..37b1097183 --- /dev/null +++ b/toolkit/content/tests/chrome/test_hiddenpaging.xul @@ -0,0 +1,161 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=317422 +--> +<window title="Mozilla Bug 317422" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <style xmlns="http://www.w3.org/1999/xhtml"> + /* This makes the richlistbox about 4.5 rows high */ + richlistitem { + height: 30px; + } + richlistbox { + height: 135px; + } + </style> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=317422" + target="_blank">Mozilla Bug 317422</a> + </body> + + <richlistbox id="richlistbox" seltype="multiple"> + <richlistitem id="richlistbox_item1"><label value="Item 1"/></richlistitem> + <richlistitem id="richlistbox_item2"><label value="Item 2"/></richlistitem> + <richlistitem id="richlistbox_item3" hidden="true"><label value="Item 3"/></richlistitem> + <richlistitem id="richlistbox_item4"><label value="Item 4"/></richlistitem> + <richlistitem id="richlistbox_item5" collapsed="true"><label value="Item 5"/></richlistitem> + <richlistitem id="richlistbox_item6"><label value="Item 6"/></richlistitem> + <richlistitem id="richlistbox_item7"><label value="Item 7"/></richlistitem> + <richlistitem id="richlistbox_item8"><label value="Item 8"/></richlistitem> + <richlistitem id="richlistbox_item9"><label value="Item 9"/></richlistitem> + <richlistitem id="richlistbox_item10"><label value="Item 10"/></richlistitem> + <richlistitem id="richlistbox_item11"><label value="Item 11"/></richlistitem> + <richlistitem id="richlistbox_item12"><label value="Item 12"/></richlistitem> + <richlistitem id="richlistbox_item13"><label value="Item 13"/></richlistitem> + <richlistitem id="richlistbox_item14"><label value="Item 14"/></richlistitem> + <richlistitem id="richlistbox_item15" hidden="true"><label value="Item 15"/></richlistitem> + </richlistbox> + + <listbox id="listbox" seltype="multiple" rows="5"> + <listitem id="listbox_item1" label="Item 1"/> + <listitem id="listbox_item2" label="Item 2"/> + <listitem id="listbox_item3" label="Item 3" hidden="true"/> + <listitem id="listbox_item4" label="Item 4"/> + <listitem id="listbox_item5" label="Item 5" hidden="true"/> + <listitem id="listbox_item6" label="Item 6"/> + <listitem id="listbox_item7" label="Item 7"/> + <listitem id="listbox_item8" label="Item 8"/> + <listitem id="listbox_item9" label="Item 9"/> + <listitem id="listbox_item10" label="Item 10"/> + <listitem id="listbox_item11" label="Item 11"/> + <listitem id="listbox_item12" label="Item 12"/> + <listitem id="listbox_item13" label="Item 13"/> + <listitem id="listbox_item14" label="Item 14"/> + <listitem id="listbox_item15" label="Item 15" hidden="true"/> + </listbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 317422 **/ +SimpleTest.waitForExplicitFinish(); + +function testRichlistbox() +{ + var id = "richlistbox"; + var listbox = document.getElementById(id); + listbox.focus(); + listbox.selectedIndex = 0; + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item7", id + ": Page down should go to the item one visible page away"); + is(listbox.getIndexOfFirstVisibleRow(), 6, id + ": Page down should have scrolled down a visible page"); + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item11", id + ": Second page down should go to the item two visible pages away"); + is(listbox.getIndexOfFirstVisibleRow(), 9, id + ": Second page down should not scroll beyond the end"); + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item14", id + ": Third page down should go to the last visible item"); + is(listbox.getIndexOfFirstVisibleRow(), 9, id + ": Third page down should not have scrolled at all"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item10", id + ": Page up should go to the item one visible page away"); + is(listbox.getIndexOfFirstVisibleRow(), 5, id + ": Page up should scroll up a visible page"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item6", id + ": Second page up should go to the item two visible pages away"); + is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Second page up should not scroll beyond the start"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item1", id + ": Third page up should return to the first visible item"); + is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Third page up should not have scrolled at all"); +} + +function testListbox() +{ + var id = "listbox"; + var listbox = document.getElementById(id); + + if (!window.matchMedia("(-moz-overlay-scrollbars)").matches) { + // Check that a scrollbar is visible by comparing the width of the listitem + // with the width of the listbox. This is a simple way to do this without + // checking the anonymous content. + ok(listbox.firstChild.getBoundingClientRect().width < listbox.getBoundingClientRect().width - 10, + id + ": Scrollbar visible"); + } + + var rowHeight = listbox.firstChild.getBoundingClientRect().height; + + listbox.focus(); + listbox.selectedIndex = 0; + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item8", id + ": Page down should go to the item one visible page away"); + is(listbox.getIndexOfFirstVisibleRow(), 7, id + ": Page down should have scrolled down a visible page"); + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item13", id + ": Second page down should go to the item two visible pages away"); + is(listbox.getIndexOfFirstVisibleRow(), 9, id + ": Second page down should not scroll beyond the end"); + sendKey("PAGE_DOWN"); + is(listbox.selectedItem.id, id + "_item14", id + ": Third page down should go to the last visible item"); + is(listbox.getIndexOfFirstVisibleRow(), 9, id + ": Third page down should not have scrolled at all"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item9", id + ": Page up should go to the item one visible page away"); + // the listScrollbox seems to go haywire when scrolling up with hidden listitems + todo_is(listbox.getIndexOfFirstVisibleRow(), 3, id + ": Page up should scroll up a visible page"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item2", id + ": Second page up should go to the item two visible pages away"); + is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Second page up should not scroll beyond the start"); + sendKey("PAGE_UP"); + is(listbox.selectedItem.id, id + "_item1", id + ": Third page up should return to the first visible item"); + is(listbox.getIndexOfFirstVisibleRow(), 0, id + ": Third page up should not have scrolled at all"); + + var scrollHeight = document.getAnonymousNodes(listbox)[1].lastChild.scrollHeight; + is(scrollHeight, rowHeight * 15, id + ": scrollHeight when rows set"); + + listbox.minHeight = 50; + scrollHeight = document.getAnonymousNodes(listbox)[1].lastChild.scrollHeight; + is(scrollHeight, rowHeight * 15, id + ": scrollHeight when rows and minimium height set"); + + listbox.removeAttribute("rows"); + + var availHeight = document.getAnonymousNodes(listbox)[1].lastChild.getBoundingClientRect().height; + // The listbox layout adds this extra height in GetPrefSize. Not sure what it's for though. + var e = (rowHeight * 15 - availHeight) % rowHeight; + var extraHeight = (e == 0) ? 0 : rowHeight - e; + + scrollHeight = document.getAnonymousNodes(listbox)[1].lastChild.scrollHeight; + is(scrollHeight, rowHeight * 15 + extraHeight, id + ": scrollHeight when minimium height set"); + + listbox.removeAttribute("minheight"); + scrollHeight = document.getAnonymousNodes(listbox)[1].lastChild.scrollHeight; + is(scrollHeight, rowHeight * 15 + extraHeight, id + ": scrollHeight"); +} + +window.onload = function runTests() { + testRichlistbox(); + testListbox(); + SimpleTest.finish(); +}; + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_keys.xul b/toolkit/content/tests/chrome/test_keys.xul new file mode 100644 index 0000000000..97cc8a2413 --- /dev/null +++ b/toolkit/content/tests/chrome/test_keys.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Keys Test" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_keys.xul", "_blank", "chrome,width=200,height=200"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_labelcontrol.xul b/toolkit/content/tests/chrome/test_labelcontrol.xul new file mode 100644 index 0000000000..b33667be05 --- /dev/null +++ b/toolkit/content/tests/chrome/test_labelcontrol.xul @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for label control="value" + --> +<window title="tabindex" width="500" height="600" + onload="runTests()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<label id="lab" control="ctl"/> +<textbox id="ctl" value="Test"/> +<checkbox id="chk" value="Checkbox"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + is($("lab").control, "ctl", "control"); + is($("lab").labeledControlElement, $("ctl"), "labeledControlElement"); + is($("ctl").labelElement, $("lab"), "labelElement"); + is($("chk").labelElement.className, "checkbox-label", "labelElement"); + + SimpleTest.finish(); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_largemenu.xul b/toolkit/content/tests/chrome/test_largemenu.xul new file mode 100644 index 0000000000..8841e2b16a --- /dev/null +++ b/toolkit/content/tests/chrome/test_largemenu.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Large Menu Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_largemenu.xul", "_blank", "chrome,width=200,height=200"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menu.xul b/toolkit/content/tests/chrome/test_menu.xul new file mode 100644 index 0000000000..877efadb1a --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu.xul @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Destruction Test" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <menubar> + <menu label="top" id="top"> + <menupopup> + <menuitem label="top item"/> + + <menu label="hello" id="nested"> + <menupopup> + <menuitem label="item1"/> + <menuitem label="item2" id="item2"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menubar> + + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function runTests() + { + var menu = document.getElementById("nested"); + + // nsIDOMXULContainerElement::getIndexOfItem(); + var item = document.getElementById("item2"); + is(menu.getIndexOfItem(item), 1, + "nsIDOMXULContainerElement::getIndexOfItem() failed."); + + // nsIDOMXULContainerElement::getItemAtIndex(); + var itemAtIdx = menu.getItemAtIndex(1); + is(itemAtIdx, item, + "nsIDOMXULContainerElement::getItemAtIndex() failed."); + + // nsIDOMXULContainerElement::itemCount + is(menu.itemCount, 2, "nsIDOMXULContainerElement::itemCount failed."); + + // nsIDOMXULContainerElement::parentContainer + var topmenu = document.getElementById("top"); + is(menu.parentContainer, topmenu, + "nsIDOMXULContainerElement::parentContainer failed."); + + // nsIDOMXULContainerElement::appendItem(); + var item = menu.appendItem("item3"); + is(menu.getIndexOfItem(item), 2, + "nsIDOMXULContainerElement::appendItem() failed."); + + // nsIDOMXULContainerElement::insertItemAt(); + var item = menu.insertItemAt(0, "itemZero"); + is(item, menu.getItemAtIndex(0), + "nsIDOMXULContainerElement::insertItemAt() failed."); + + // nsIDOMXULContainerElement::removeItemAt(); + var item = menu.removeItemAt(0); + is(3, menu.itemCount, + "nsIDOMXULContainerElement::removeItemAt() failed."); + + SimpleTest.finish(); + } + + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"> + </p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + +</window> + diff --git a/toolkit/content/tests/chrome/test_menu_anchored.xul b/toolkit/content/tests/chrome/test_menu_anchored.xul new file mode 100644 index 0000000000..8fdda3f25f --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu_anchored.xul @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + Test for menus with the anchor attribute set + --> +<window title="Anchored Menus Test" + align="start" + onload="setTimeout(runTest, 0,'tb1');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"/> + +<hbox> + +<toolbarbutton id="tb1" type="menu-button" label="Open" anchor="dropmarker"> + <menupopup id="popup1" + onpopupshown="checkPopup(this, document.getAnonymousElementByAttribute(this.parentNode, 'anonid', 'dropmarker'))" + onpopuphidden="runTest('tb2')"> + <menuitem label="Item"/> + </menupopup> +</toolbarbutton> + +<toolbarbutton id="tb2" type="menu-button" label="Open" anchor="someanchor"> + <menupopup id="popup2" onpopupshown="checkPopup(this, $('someanchor'))" onpopuphidden="runTest('tb3')"> + <menuitem label="Item"/> + </menupopup> +</toolbarbutton> + +<toolbarbutton id="tb3" type="menu-button" label="Open" anchor="noexist"> + <menupopup id="popup3" onpopupshown="checkPopup(this, this.parentNode)" onpopuphidden="SimpleTest.finish()"> + <menuitem label="Item"/> + </menupopup> +</toolbarbutton> + +</hbox> + +<hbox pack="end" width="180"> + <button id="someanchor" label="Anchor"/> +</hbox> + +<!-- test results are displayed in the html:body --> +<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"><![CDATA[ + +function runTest(menuid) +{ + let menu = $(menuid); + let dropmarker = document.getAnonymousElementByAttribute(menu, "anonid", "dropmarker"); + + synthesizeMouseAtCenter(dropmarker, { }); +} + +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +function checkPopup(popup, anchor) +{ + let popupRect = popup.getBoundingClientRect(); + let anchorRect = anchor.getBoundingClientRect(); + + ok(isWithinHalfPixel(popupRect.left, anchorRect.left), popup.id + " left"); + ok(isWithinHalfPixel(popupRect.top, anchorRect.bottom), popup.id + " top"); + + popup.hidePopup(); +} + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_menu_hide.xul b/toolkit/content/tests/chrome/test_menu_hide.xul new file mode 100644 index 0000000000..b5ce934db0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menu_hide.xul @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Destruction Test" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<menu id="menu"> + <menupopup onpopupshown="this.firstChild.open = true" onpopuphidden="if (event.target == this) done()"> + <menu id="submenu" label="One"> + <menupopup onpopupshown="submenuOpened();"> + <menuitem label="Two"/> + </menupopup> + </menu> + </menupopup> +</menu> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + $("menu").open = true; +} + +function submenuOpened() +{ + var submenu = $("submenu") + is(submenu.getAttribute('_moz-menuactive'), "true", "menu highlighted"); + submenu.hidden = true; + $("menu").open = false; +} + +function done() +{ + ok(!$("submenu").hasAttribute('_moz-menuactive'), "menu unhighlighted"); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menuchecks.xul b/toolkit/content/tests/chrome/test_menuchecks.xul new file mode 100644 index 0000000000..8128b738ca --- /dev/null +++ b/toolkit/content/tests/chrome/test_menuchecks.xul @@ -0,0 +1,147 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Checkbox and Radio Tests" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <button id="menu" type="menu" label="View"> + <menupopup id="popup" onpopupshown="popupShown()" onpopuphidden="popupHidden()"> + <menuitem id="toolbar" label="Show Toolbar" type="checkbox"/> + <menuitem id="statusbar" label="Show Status Bar" type="checkbox" checked="true"/> + <menuitem id="bookmarks" label="Show Bookmarks" type="checkbox" autocheck="false"/> + <menuitem id="history" label="Show History" type="checkbox" autocheck="false" checked="true"/> + <menuseparator/> + <menuitem id="byname" label="By Name" type="radio" name="sort"/> + <menuitem id="bydate" label="By Date" type="radio" name="sort" checked="true"/> + <menuseparator/> + <menuitem id="ascending" label="Ascending" type="radio" name="order" checked="true"/> + <menuitem id="descending" label="Descending" type="radio" name="order" autocheck="false"/> + <menuitem id="bysubject" label="By Subject" type="radio" name="sort"/> + </menupopup> + </button> + + </hbox> + + <!-- + This test checks that checkbox and radio menu items work properly + --> + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + var gTestIndex = 0; + + // tests to perform + var tests = [ + { + testname: "select unchecked checkbox", + item: "toolbar", + checked: ["toolbar", "statusbar", "history", "bydate", "ascending"] + }, + { + testname: "select checked checkbox", + item: "statusbar", + checked: ["toolbar", "history", "bydate", "ascending"] + }, + { + testname: "select unchecked autocheck checkbox", + item: "bookmarks", + checked: ["toolbar", "history", "bydate", "ascending"] + }, + { + testname: "select checked autocheck checkbox", + item: "history", + checked: ["toolbar", "history", "bydate", "ascending"] + }, + { + testname: "select unchecked radio", + item: "byname", + checked: ["toolbar", "history", "byname", "ascending"] + }, + { + testname: "select checked radio", + item: "byname", + checked: ["toolbar", "history", "byname", "ascending"] + }, + { + testname: "select out of order checked radio", + item: "bysubject", + checked: ["toolbar", "history", "bysubject", "ascending"] + }, + { + testname: "select first radio again", + item: "byname", + checked: ["toolbar", "history", "byname", "ascending"] + }, + { + testname: "select autocheck radio", + item: "descending", + checked: ["toolbar", "history", "byname", "ascending"] + } + ]; + + function runTest() + { + checkMenus(["statusbar", "history", "bydate", "ascending"], "initial"); + document.getElementById("menu").open = true; + } + + function checkMenus(checkedItems, testname) + { + var isok = true; + var children = document.getElementById("popup").childNodes; + for (var c = 0; c < children.length; c++) { + var child = children[c]; + if ((checkedItems.indexOf(child.id) != -1 && child.getAttribute("checked") != "true") || + (checkedItems.indexOf(child.id) == -1 && child.hasAttribute("checked"))) { + isok = false; + break; + } + } + + ok(isok, testname); + } + + function popupShown() + { + var test = tests[gTestIndex]; + synthesizeMouse(document.getElementById(test.item), 4, 4, { }); + } + + function popupHidden() + { + if (gTestIndex < tests.length) { + var test = tests[gTestIndex]; + checkMenus(test.checked, test.testname); + gTestIndex++; + if (gTestIndex < tests.length) { + document.getElementById("menu").open = true; + } + else { + // manually setting the checkbox should also update the radio state + document.getElementById("bydate").setAttribute("checked", "true"); + checkMenus(["toolbar", "history", "bydate", "ascending"], "set checked attribute on radio"); + SimpleTest.finish(); + } + } + } + + ]]> + </script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menuitem_blink.xul b/toolkit/content/tests/chrome/test_menuitem_blink.xul new file mode 100644 index 0000000000..319c284fd7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menuitem_blink.xul @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Blinking Context Menu Item Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <menulist id="menulist"> + <menupopup id="menupopup"> + <menuitem label="Menu Item" id="menuitem"/> + </menupopup> + </menulist> +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(startTest); + +function startTest() { + if (!/Mac/.test(navigator.platform)) { + ok(true, "Nothing to test on non-Mac."); + SimpleTest.finish(); + return; + } + // Destroy frame while removing the _moz-menuactive attribute. + test_crash("REMOVAL", test2); +} + +function test2() { + // Destroy frame while adding the _moz-menuactive attribute. + test_crash("ADDITION", test3); +} + +function test3() { + // Don't mess with the frame, just test whether we've blinked. + test_crash("", SimpleTest.finish); +} + +function test_crash(when, andThen) { + var menupopup = document.getElementById("menupopup"); + var menuitem = document.getElementById("menuitem"); + var attrChanges = { "REMOVAL": 0, "ADDITION": 0 }; + var storedEvent = null; + menupopup.addEventListener("popupshown", function () { + menupopup.removeEventListener("popupshown", arguments.callee, false); + menuitem.addEventListener("mouseup", function (e) { + menuitem.removeEventListener("mouseup", arguments.callee, true); + menuitem.addEventListener("DOMAttrModified", function (e) { + if (e.attrName == "_moz-menuactive") { + if (!attrChanges[e.attrChange]) + attrChanges[e.attrChange] = 1; + else + attrChanges[e.attrChange]++; + storedEvent = e; + if (e.attrChange == e[when]) { + menuitem.hidden = true; + menuitem.getBoundingClientRect(); + ok(true, "Didn't crash on _moz-menuactive " + when.toLowerCase() + " during blinking") + menuitem.hidden = false; + menuitem.removeEventListener("DOMAttrModified", arguments.callee, false); + SimpleTest.executeSoon(function () { + menupopup.hidePopup(); + }); + } + } + }, false); + }, true); + menupopup.addEventListener("popuphidden", function() { + menupopup.removeEventListener("popuphidden", arguments.callee, false); + if (!when) { + // Test whether we've blinked at all. + var shouldBlink = navigator.platform.match(/Mac/); + var expectedNumRemoval = shouldBlink ? 2 : 1; + var expectedNumAddition = shouldBlink ? 1 : 0; + ok(storedEvent, "got DOMAttrModified events after clicking menuitem") + is(attrChanges[storedEvent.REMOVAL], expectedNumRemoval, "blinking unset attributes correctly"); + is(attrChanges[storedEvent.ADDITION], expectedNumAddition, "blinking set attributes correctly"); + } + SimpleTest.executeSoon(andThen); + }, false); + synthesizeMouse(menuitem, 10, 5, { type : "mousemove" }); + synthesizeMouse(menuitem, 10, 5, { type : "mousemove" }); + synthesizeMouse(menuitem, 10, 5, { type : "mousedown" }); + SimpleTest.executeSoon(function () { + synthesizeMouse(menuitem, 10, 5, { type : "mouseup" }); + }); + }, false); + document.getElementById("menulist").open = true; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menuitem_commands.xul b/toolkit/content/tests/chrome/test_menuitem_commands.xul new file mode 100644 index 0000000000..e31774ccc3 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menuitem_commands.xul @@ -0,0 +1,104 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menuitem Commands Test" + onload="runOrOpen()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function checkAttributes(elem, label, accesskey, disabled, hidden, isAfter) +{ + var is = window.opener.wrappedJSObject.SimpleTest.is; + + is(elem.getAttribute("label"), label, elem.id + " label " + (isAfter ? "after" : "before") + " open"); + is(elem.getAttribute("accesskey"), accesskey, elem.id + " accesskey " + (isAfter ? "after" : "before") + " open"); + is(elem.getAttribute("disabled"), disabled, elem.id + " disabled " + (isAfter ? "after" : "before") + " open"); + is(elem.getAttribute("hidden"), hidden, elem.id + " hidden " + (isAfter ? "after" : "before") + " open"); +} + +function runOrOpen() +{ + if (window.opener) { + SimpleTest.waitForFocus(runTest); + } + else { + window.open("test_menuitem_commands.xul", "", "chrome"); + } +} + +function runTest() +{ + runTestSet(""); + runTestSet("bar"); + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); +} + +function runTestSet(suffix) +{ + var isMac = (navigator.platform.indexOf("Mac") >= 0); + + var one = $("one" + suffix); + var two = $("two" + suffix); + var three = $("three" + suffix); + var four = $("four" + suffix); + + checkAttributes(one, "One", "", "", "true", false); + checkAttributes(two, "", "", "false", "", false); + checkAttributes(three, "Three", "T", "true", "", false); + checkAttributes(four, "Four", "F", "", "", false); + + if (isMac && suffix) { + var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor). + getInterface(Components.interfaces.nsIDOMWindowUtils); + utils.forceUpdateNativeMenuAt("0"); + } + else { + $("menu" + suffix).open = true; + } + + checkAttributes(one, "One", "", "", "false", true); + checkAttributes(two, "Cat", "C", "", "", true); + checkAttributes(three, "Dog", "D", "false", "true", true); + checkAttributes(four, "Four", "F", "true", "", true); + + $("menu" + suffix).open = false; +} +]]> +</script> + +<command id="cmd_one" hidden="false"/> +<command id="cmd_two" label="Cat" accesskey="C"/> +<command id="cmd_three" label="Dog" accesskey="D" disabled="false" hidden="true"/> +<command id="cmd_four" disabled="true"/> + +<button id="menu" type="menu"> + <menupopup> + <menuitem id="one" label="One" hidden="true" command="cmd_one"/> + <menuitem id="two" disabled="false" command="cmd_two"/> + <menuitem id="three" label="Three" accesskey="T" disabled="true" command="cmd_three"/> + <menuitem id="four" label="Four" accesskey="F" command="cmd_four"/> + </menupopup> +</button> + +<menubar> + <menu id="menubar" label="Sample"> + <menupopup> + <menuitem id="onebar" label="One" hidden="true" command="cmd_one"/> + <menuitem id="twobar" disabled="false" command="cmd_two"/> + <menuitem id="threebar" label="Three" accesskey="T" disabled="true" command="cmd_three"/> + <menuitem id="fourbar" label="Four" accesskey="F" command="cmd_four"/> + </menupopup> + </menu> +</menubar> + +<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist.xul b/toolkit/content/tests/chrome/test_menulist.xul new file mode 100644 index 0000000000..4e3817d89c --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist.xul @@ -0,0 +1,314 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Tests" + onload="setTimeout(testtag_menulists, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"></script> + +<vbox id="scroller" style="overflow: auto" height="60"> + <menulist id="menulist" onpopupshown="test_menulist_open(this, this.parentNode)" + onpopuphidden="$('menulist-in-listbox').open = true;"> + <menupopup id="menulist-popup"/> + </menulist> + <button label="Two"/> + <button label="Three"/> +</vbox> +<listbox id="scroller-in-listbox" style="overflow: auto" height="60"> + <listitem allowevents="true"> + <menulist id="menulist-in-listbox" onpopupshown="test_menulist_open(this, this.parentNode.parentNode)" + onpopuphidden="SimpleTest.executeSoon(checkScrollAndFinish)"> + <menupopup id="menulist-in-listbox-popup"> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + </menupopup> + </menulist> + </listitem> + <listitem label="Two"/> + <listitem label="Three"/> + <listitem label="Four"/> + <listitem label="Five"/> + <listitem label="Six"/> +</listbox> + +<hbox> + <menulist id="menulist-size"> + <menupopup> + <menuitem label="Menuitem Label" width="200"/> + </menupopup> + </menulist> +</hbox> + +<menulist id="menulist-editable" editable="true"> + <menupopup id="menulist-popup-editable"/> +</menulist> + +<menulist id="menulist-initwithvalue" value="two"> + <menupopup> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three"/> + </menupopup> +</menulist> +<menulist id="menulist-initwithselected" value="two"> + <menupopup> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three" selected="true"/> + </menupopup> +</menulist> +<menulist id="menulist-editable-initwithvalue" editable="true" value="Two"> + <menupopup> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three"/> + </menupopup> +</menulist> +<menulist id="menulist-editable-initwithselected" editable="true" value="two"> + <menupopup> + <menuitem label="One" value="one"/> + <menuitem label="Two" value="two"/> + <menuitem label="Three" value="three" selected="true"/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function testtag_menulists() +{ + testtag_menulist_UI_start($("menulist"), false); +} + +function testtag_menulist_UI_start(element, editable) +{ + var testprefix = editable ? "editable" : ""; + + // check the menupopup property + var popup = element.menupopup; + ok(popup && popup.localName == "menupopup" && + popup.parentNode == element, testprefix + " menupopup"); + + // test the interfaces that menulist implements + test_nsIDOMXULMenuListElement(element, testprefix, editable); +} + +function testtag_menulist_UI_finish(element, editable) +{ + element.value = ""; + + test_nsIDOMXULSelectControlElement(element, "menuitem", + editable ? "editable" : null); + + if (!editable) { + testtag_menulist_UI_start($("menulist-editable"), true); + } + else { + // bug 566154, the menulist width should account for vertical scrollbar + ok(document.getElementById("menulist-size").getBoundingClientRect().width >= 210, + "menulist popup width includes scrollbar width"); + + $("menulist").open = true; + } +} + +function test_nsIDOMXULMenuListElement(element, testprefix, editable) +{ + is(element.open, false, testprefix + " open"); + is(element.editable, editable, testprefix + " editable"); + + if (editable) { + var inputField = element.inputField; + is(inputField && + inputField instanceof Components.interfaces.nsIDOMHTMLInputElement, + true, testprefix + " inputField"); + + // check if the select method works + inputField.select(); + is(inputField.selectionStart, 0, testprefix + " empty select selectionStart"); + is(inputField.selectionEnd, 0, testprefix + " empty select selectionEnd"); + + element.value = "Some Text"; + inputField.select(); + is(inputField.selectionStart, 0, testprefix + " empty select selectionStart"); + is(inputField.selectionEnd, 9, testprefix + " empty select selectionEnd"); + } + else { + is(element.inputField, null , testprefix + " inputField"); + } + + element.appendItem("Item One", "one"); + var seconditem = element.appendItem("Item Two", "two"); + var thirditem = element.appendItem("Item Three", "three"); + element.appendItem("Item Four", "four"); + + seconditem.image = "happy.png"; + seconditem.setAttribute("description", "This is the second description"); + thirditem.image = "happy.png"; + thirditem.setAttribute("description", "This is the third description"); + + // check the image and description properties + // editable menulists don't use the image or description properties currently + if (editable) { + element.selectedIndex = 1; + is(element.image, "", testprefix + " image set to selected"); + is(element.description, "", testprefix + " description set to selected"); + test_nsIDOMXULMenuListElement_finish(element, testprefix, editable); + } + else { + element.selectedIndex = 1; + is(element.image, "happy.png", testprefix + " image set to selected"); + is(element.description, "This is the second description", testprefix + " description set to selected"); + element.selectedIndex = -1; + is(element.image, "", testprefix + " image set when none selected"); + is(element.description, "", testprefix + " description set when none selected"); + element.selectedIndex = 2; + is(element.image, "happy.png", testprefix + " image set to selected again"); + is(element.description, "This is the third description", testprefix + " description set to selected again"); + + // check that changing the properties of the selected item changes the menulist's properties + let properties = [{attr: "label", value: "Item Number Three"}, + {attr: "value", value: "item-three"}, + {attr: "image", value: "smile.png"}, + {attr: "description", value: "Changed description"}]; + test_nsIDOMXULMenuListElement_properties(element, testprefix, editable, thirditem, properties); + } +} + +function test_nsIDOMXULMenuListElement_properties(element, testprefix, editable, thirditem, properties) +{ + let {attr, value} = properties.shift(); + let last = (properties.length == 0); + + let mutObserver = new MutationObserver(() => { + is(element.getAttribute(attr), value, `${testprefix} ${attr} modified`); + done(); + }); + mutObserver.observe(element, { attributeFilter: [attr] }); + + let failureTimeout = setTimeout(() => { + ok(false, `${testprefix} ${attr} should have updated`); + done(); + }, 2000); + + function done() + { + clearTimeout(failureTimeout); + mutObserver.disconnect(); + if (!last) { + test_nsIDOMXULMenuListElement_properties(element, testprefix, editable, thirditem, properties); + } + else { + test_nsIDOMXULMenuListElement_unselected(element, testprefix, editable, thirditem); + } + } + + thirditem.setAttribute(attr, value) +} + +function test_nsIDOMXULMenuListElement_unselected(element, testprefix, editable, thirditem) +{ + let seconditem = thirditem.previousElementSibling; + seconditem.label = "Changed Label 2"; + is(element.label, "Item Number Three", testprefix + " label of another item modified"); + + element.selectedIndex = 0; + is(element.image, "", testprefix + " image set to selected with no image"); + is(element.description, "", testprefix + " description set to selected with no description"); + test_nsIDOMXULMenuListElement_finish(element, testprefix, editable); +} + +function test_nsIDOMXULMenuListElement_finish(element, testprefix, editable) +{ + // check the removeAllItems method + element.appendItem("An Item", "anitem"); + element.appendItem("Another Item", "anotheritem"); + element.removeAllItems(); + is(element.itemCount, 0, testprefix + " removeAllItems"); + + testtag_menulist_UI_finish(element, editable); +} + +function test_menulist_open(element, scroller) +{ + element.appendItem("Scroll Item 1", "scrollitem1"); + element.appendItem("Scroll Item 2", "scrollitem2"); + element.focus(); + element.selectedIndex = 0; + +/* + // bug 530504, mousewheel while menulist is open should not scroll menulist + // items or parent + var scrolled = false; + var mouseScrolled = function (event) { scrolled = true; } + window.addEventListener("DOMMouseScroll", mouseScrolled, false); + synthesizeWheel(element, 2, 2, { deltaY: 10, + deltaMode: WheelEvent.DOM_DELTA_LINE }); + is(scrolled, true, "mousescroll " + element.id); + is(scroller.scrollTop, 0, "scroll position on mousescroll " + element.id); + window.removeEventListener("DOMMouseScroll", mouseScrolled, false); +*/ + + // bug 543065, hovering the mouse over an item should highlight it, not + // scroll the parent, and not change the selected index. + var item = element.menupopup.childNodes[1]; + + synthesizeMouse(element.menupopup.childNodes[1], 2, 2, { type: "mousemove" }); + synthesizeMouse(element.menupopup.childNodes[1], 6, 6, { type: "mousemove" }); + is(element.menuBoxObject.activeChild, item, "activeChild after menu highlight " + element.id); + is(element.selectedIndex, 0, "selectedIndex after menu highlight " + element.id); + is(scroller.scrollTop, 0, "scroll position after menu highlight " + element.id); + + element.open = false; +} + +function checkScrollAndFinish() +{ + is($("scroller").scrollTop, 0, "mousewheel on menulist does not scroll vbox parent"); + is($("scroller-in-listbox").scrollTop, 0, "mousewheel on menulist does not scroll listbox parent"); + + // bug 561243, outline causes the mouse click to be targeted incorrectly + var editableMenulist = $("menulist-editable"); + editableMenulist.className = "outlined"; + + synthesizeMouse(editableMenulist.inputField, 25, 8, { type: "mousedown" }); + synthesizeMouse(editableMenulist.inputField, 25, 8, { type: "mouseup" }); + isnot(editableMenulist.inputField.selectionStart, editableMenulist.inputField.textLength, + "mouse event on editable menulist with outline caret position"); + + let menulist = $("menulist-size"); + menulist.addEventListener("popupshown", function testAltClose() { + menulist.removeEventListener("popupshown", testAltClose); + + sendKey("ALT"); + is(menulist.menupopup.state, "open", "alt doesn't close menulist"); + menulist.open = false; + + SimpleTest.finish(); + }); + + menulist.open = true; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<style> +.outlined > .menulist-editable-box { outline: 1px solid black; } +</style> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_keynav.xul b/toolkit/content/tests/chrome/test_menulist_keynav.xul new file mode 100644 index 0000000000..c2e404c09a --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_keynav.xul @@ -0,0 +1,272 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Key Navigation Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<button id="button1" label="One"/> +<menulist id="list"> + <menupopup id="popup" onpopupshowing="return gShowPopup;"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> + <menuitem id="i2b" disabled="true" label="Two and a Half"/> + <menuitem id="i3" label="Three"/> + <menuitem id="i4" label="Four"/> + </menupopup> +</menulist> +<button id="button2" label="Two"/> +<menulist id="list2"> + <menupopup id="popup" onpopupshown="checkCursorNavigation();"> + <menuitem id="b1" label="One"/> + <menuitem id="b2" label="Two" selected="true"/> + <menuitem id="b3" label="Three"/> + <menuitem id="b4" label="Four"/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gShowPopup = false; +var gModifiers = 0; +var gOpenPhase = false; + +var list = $("list"); +let expectCommandEvent; + +var iswin = (navigator.platform.indexOf("Win") == 0); +var ismac = (navigator.platform.indexOf("Mac") == 0); + +function runTests() +{ + list.focus(); + + // on Mac, up and cursor keys open the menu, but on other platforms, the + // cursor keys navigate between items without opening the menu + if (navigator.platform.indexOf("Mac") == -1) { + expectCommandEvent = true; + keyCheck(list, "VK_DOWN", 2, "cursor down"); + keyCheck(list, "VK_DOWN", 3, "cursor down skip disabled"); + keyCheck(list, "VK_UP", 2, "cursor up skip disabled"); + keyCheck(list, "VK_UP", 1, "cursor up"); + keyCheck(list, "VK_UP", 4, "cursor up wrap"); + keyCheck(list, "VK_DOWN", 1, "cursor down wrap"); + } + + // check that attempting to open the menulist does not change the selection + synthesizeKey("VK_DOWN", { altKey: navigator.platform.indexOf("Mac") == -1 }); + is(list.selectedItem, $("i1"), "open menulist down selectedItem"); + synthesizeKey("VK_UP", { altKey: navigator.platform.indexOf("Mac") == -1 }); + is(list.selectedItem, $("i1"), "open menulist up selectedItem"); + + list.selectedItem = $("i1"); + + pressLetter(); +} + +function pressLetter() +{ + // A command event should be fired only if the menulist is closed, or on Windows, + // where items are selected immediately. + expectCommandEvent = !gOpenPhase || iswin; + + synthesizeKey("G", { }); + is(list.selectedItem, $("i1"), "letter pressed not found selectedItem"); + + keyCheck(list, "T", 2, "letter pressed"); + + if (!gOpenPhase) { + SpecialPowers.setIntPref("ui.menu.incremental_search.timeout", 0); // prevent to timeout + keyCheck(list, "T", 2, "same letter pressed"); + SpecialPowers.clearUserPref("ui.menu.incremental_search.timeout"); + } + + setTimeout(pressedAgain, 1200); +} + +function pressedAgain() +{ + keyCheck(list, "T", 3, "letter pressed again"); + SpecialPowers.setIntPref("ui.menu.incremental_search.timeout", 0); // prevent to timeout + keyCheck(list, "W", 2, "second letter pressed"); + SpecialPowers.clearUserPref("ui.menu.incremental_search.timeout"); + setTimeout(differentPressed, 1200); +} + +function differentPressed() +{ + keyCheck(list, "O", 1, "different letter pressed"); + + if (gOpenPhase) { + list.open = false; + tabAndScroll(); + } + else { + // Run the letter tests again with the popup open + info("list open phase"); + + list.selectedItem = $("i1"); + + // Hide and show the list to avoid using any existing incremental key state. + list.hidden = true; + list.clientWidth; + list.hidden = false; + + gShowPopup = true; + gOpenPhase = true; + + list.addEventListener("popupshown", function popupShownListener() { + list.removeEventListener("popupshown", popupShownListener, false); + pressLetter(); + }, false); + + list.open = true; + } +} + +function tabAndScroll() +{ + list = $("list"); + + if (navigator.platform.indexOf("Mac") == -1) { + $("button1").focus(); + synthesizeKeyExpectEvent("VK_TAB", { }, list, "focus", "focus to menulist"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("button2"), "focus", "focus to button"); + is(document.activeElement, $("button2"), "tab from menulist focused button"); + } + + // now make sure that using a key scrolls the menu correctly + + for (let i = 0; i < 65; i++) { + list.appendItem("Item" + i, "item" + i); + } + list.open = true; + is(list.getBoundingClientRect().width, list.firstChild.getBoundingClientRect().width, + "menu and popup width match"); + var minScrollbarWidth = window.matchMedia("(-moz-overlay-scrollbars)").matches ? 0 : 3; + ok(list.getBoundingClientRect().width >= list.getItemAtIndex(0).getBoundingClientRect().width + minScrollbarWidth, + "menuitem width accounts for scrollbar"); + list.open = false; + + list.menupopup.maxHeight = 100; + list.open = true; + + var rowdiff = list.getItemAtIndex(1).getBoundingClientRect().top - + list.getItemAtIndex(0).getBoundingClientRect().top; + + var item = list.getItemAtIndex(10); + var originalPosition = item.getBoundingClientRect().top; + + list.menuBoxObject.activeChild = item; + ok(item.getBoundingClientRect().top < originalPosition, + "position of item 1: " + item.getBoundingClientRect().top + " -> " + originalPosition); + + originalPosition = item.getBoundingClientRect().top; + + synthesizeKey("VK_DOWN", { }); + is(item.getBoundingClientRect().top, originalPosition - rowdiff, "position of item 10"); + + list.open = false; + + checkEnter(); +} + +function keyCheck(list, key, index, testname) +{ + var item = $("i" + index); + synthesizeKeyExpectEvent(key, { }, item, expectCommandEvent ? "command" : "!command", testname); + is(list.selectedItem, expectCommandEvent ? item : $("i1"), testname + " selectedItem"); +} + +function checkModifiers(event) +{ + var expectedModifiers = (gModifiers == 1); + is(event.shiftKey, expectedModifiers, "shift key pressed"); + is(event.ctrlKey, expectedModifiers, "ctrl key pressed"); + is(event.altKey, expectedModifiers, "alt key pressed"); + is(event.metaKey, expectedModifiers, "meta key pressed"); + gModifiers++; +} + +function checkEnter() +{ + list.addEventListener("popuphidden", checkEnterWithModifiers, false); + list.addEventListener("command", checkModifiers, false); + list.open = true; + synthesizeKey("VK_RETURN", { }); +} + +function checkEnterWithModifiers() +{ + is(gModifiers, 1, "modifiers checked when not set"); + + ok(!list.open, "list closed on enter press"); + list.removeEventListener("popuphidden", checkEnterWithModifiers, false); + + list.addEventListener("popuphidden", verifyPopupOnClose, false); + list.open = true; + + synthesizeKey("VK_RETURN", { shiftKey: true, ctrlKey: true, altKey: true, metaKey: true }); +} + +function verifyPopupOnClose() +{ + is(gModifiers, 2, "modifiers checked when set"); + + ok(!list.open, "list closed on enter press with modifiers"); + list.removeEventListener("popuphidden", verifyPopupOnClose, false); + + list = $("list2"); + list.focus(); + list.open = true; +} + +function checkCursorNavigation() +{ + var commandEventsCount = 0; + list.addEventListener("command", event => { + is(event.target, list.selectedItem, "command event fired on selected item"); + commandEventsCount++; + }, false); + + is(list.selectedIndex, 1, "selectedIndex before cursor down"); + synthesizeKey("VK_DOWN", { }); + is(list.selectedIndex, iswin ? 2 : 1, "selectedIndex after cursor down"); + is(commandEventsCount, iswin ? 1 : 0, "selectedIndex after cursor down command event"); + is(list.menupopup.state, "open", "cursor down popup state"); + synthesizeKey("VK_PAGE_DOWN", { }); + is(list.selectedIndex, iswin ? 3 : 1, "selectedIndex after page down"); + is(commandEventsCount, iswin ? 2 : 0, "selectedIndex after page down command event"); + is(list.menupopup.state, "open", "page down popup state"); + + synthesizeKey("VK_UP", { altKey: true }); + is(list.open, ismac, "alt+up closes popup"); + + if (ismac) { + list.open = false; + } + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_null_value.xul b/toolkit/content/tests/chrome/test_menulist_null_value.xul new file mode 100644 index 0000000000..2545b6cdec --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_null_value.xul @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist value property" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<menulist id="list"> + <menupopup> + <menuitem id="i0" label="Zero" value="0"/> + <menuitem id="i1" label="One" value="item1"/> + <menuitem id="i2" label="Two" value="item2"/> + <menuitem id="ifalse" label="False" value="false"/> + <menuitem id="iempty" label="Empty" value=""/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + var list = document.getElementById("list"); + + list.value = "item2"; + is(list.value, "item2", "Check list value after setting value"); + is(list.getAttribute("label"), "Two", "Check list label after setting value"); + + list.selectedItem = null; + is(list.value, "", "Check list value after setting selectedItem to null"); + is(list.getAttribute("label"), "", "Check list label after setting selectedItem to null"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + is(list.value, "item1", "Check list value after setting selectedIndex"); + is(list.getAttribute("label"), "One", "Check list label after setting selectedIndex"); + + // check that an item can have the "false" value + list.value = false; + is(list.value, "false", "Check list value after setting it to false"); + is(list.getAttribute("label"), "False", "Check list labem after setting value to false"); + + // check that an item can have the "0" value + list.value = 0; + is(list.value, "0", "Check list value after setting it to 0"); + is(list.getAttribute("label"), "Zero", "Check list label after setting value to 0"); + + // check that an item can have the empty string value. + list.value = ""; + is(list.value, "", "Check list value after setting it to an empty string"); + is(list.getAttribute("label"), "Empty", "Check list label after setting value to an empty string"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + // set the value to null and test it (bug 408940) + list.value = null; + is(list.value, "", "Check list value after setting value to null"); + is(list.getAttribute("label"), "", "Check list label after setting value to null"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + // set the value to undefined and test it (bug 408940) + list.value = undefined; + is(list.value, "", "Check list value after setting value to undefined"); + is(list.getAttribute("label"), "", "Check list label after setting value to undefined"); + + // select something again to make sure the label is not already empty + list.selectedIndex = 1; + // set the value to something that does not exist in any menuitem of the list + // and make sure the previous label is removed + list.value = "this does not exist"; + is(list.value, "this does not exist", "Check the list value after setting it to something not associated witn an existing menuitem"); + is(list.getAttribute("label"), "", "Check that the list label is empty after selecting a nonexistent item"); + + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_paging.xul b/toolkit/content/tests/chrome/test_menulist_paging.xul new file mode 100644 index 0000000000..c58e0328f1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_paging.xul @@ -0,0 +1,163 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist Tests" + onload="setTimeout(startTest, 0);" + onpopupshown="menulistShown()" onpopuphidden="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<menulist id="menulist1"> + <menupopup id="menulist-popup1"> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <menuitem label="Ten"/> + </menupopup> +</menulist> + +<menulist id="menulist2"> + <menupopup id="menulist-popup2"> + <menuitem label="One" disabled="true"/> + <menuitem label="Two" selected="true"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <menuitem label="Ten" disabled="true"/> + </menupopup> +</menulist> + +<menulist id="menulist3"> + <menupopup id="menulist-popup3"> + <label value="One"/> + <menuitem label="Two" selected="true"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five" disabled="true"/> + <menuitem label="Six" disabled="true"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <label value="Ten"/> + </menupopup> +</menulist> + +<menulist id="menulist4"> + <menupopup id="menulist-popup4"> + <label value="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + <menuitem label="Six" selected="true"/> + <menuitem label="Seven"/> + <menuitem label="Eight"/> + <menuitem label="Nine"/> + <label value="Ten"/> + </menupopup> +</menulist> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +let test; + +// Fields: +// list - menulist id +// initial - initial selected index +// scroll - index of item at top of the visible scrolled area, -1 to skip this test +// downs - array of indicies that will be selected when pressing down in sequence +// ups - array of indicies that will be selected when pressing up in sequence +let tests = [ + { list: "menulist1", initial: 0, scroll: 0, downs: [3, 6, 9, 9], + ups: [6, 3, 0, 0] }, + { list: "menulist2", initial: 1, scroll: 0, downs: [4, 7, 8, 8], + ups: [5, 2, 1] }, + { list: "menulist3", initial: 1, scroll: -1, downs: [6, 8, 8], + ups: [3, 1, 1] }, + { list: "menulist4", initial: 5, scroll: 2, downs: [], ups: [] } +]; + +function startTest() +{ + let popup = document.getElementById("menulist-popup1"); + let menupopupHeight = popup.getBoundingClientRect().height; + let menuitemHeight = popup.firstChild.getBoundingClientRect().height; + + // First, set the height of each popup to the height of four menuitems plus + // any padding and border on the menupopup. + let height = menuitemHeight * 4 + (menupopupHeight - menuitemHeight * 10); + popup.height = height; + document.getElementById("menulist-popup2").height = height; + document.getElementById("menulist-popup3").height = height; + document.getElementById("menulist-popup4").height = height; + + runTest(); +} + +function runTest() +{ + if (!tests.length) { + SimpleTest.finish(); + return; + } + + test = tests.shift(); + document.getElementById(test.list).open = true; +} + +function menulistShown() +{ + let menulist = document.getElementById(test.list); + is(menulist.menuBoxObject.activeChild.label, menulist.getItemAtIndex(test.initial).label, test.list + " initial selection"); + + let cs = window.getComputedStyle(menulist.menupopup); + let bpTop = parseFloat(cs.paddingTop) + parseFloat(cs.borderTopWidth); + + // Skip menulist3 as it has a label that scrolling doesn't need normally deal with. + if (test.scroll >= 0) { + is(menulist.menupopup.childNodes[test.scroll].getBoundingClientRect().top, + menulist.menupopup.getBoundingClientRect().top + bpTop, + "Popup scroll at correct position"); + } + + for (let i = 0; i < test.downs.length; i++) { + sendKey("PAGE_DOWN"); + is(menulist.menuBoxObject.activeChild.label, menulist.getItemAtIndex(test.downs[i]).label, test.list + " page down " + i); + } + + for (let i = 0; i < test.ups.length; i++) { + sendKey("PAGE_UP"); + is(menulist.menuBoxObject.activeChild.label, menulist.getItemAtIndex(test.ups[i]).label, test.list + " page up " + i); + } + + menulist.open = false; +} +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_menulist_position.xul b/toolkit/content/tests/chrome/test_menulist_position.xul new file mode 100644 index 0000000000..a146cb85ef --- /dev/null +++ b/toolkit/content/tests/chrome/test_menulist_position.xul @@ -0,0 +1,97 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menulist position Test" + onload="setTimeout(init, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks the position of a menulist's popup. + --> + +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var menulist; + +function init() +{ + menulist = document.getElementById("menulist"); + menulist.open = true; +} + +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +function popupShown() +{ + var menurect = menulist.getBoundingClientRect(); + var popuprect = menulist.menupopup.getBoundingClientRect(); + + let marginLeft = parseFloat(getComputedStyle(menulist.menupopup).marginLeft); + ok(isWithinHalfPixel(menurect.left + marginLeft, popuprect.left), "left position"); + ok(isWithinHalfPixel(menurect.right + marginLeft, popuprect.right), "right position"); + + let index = menulist.selectedIndex; + if (menulist.selectedItem && navigator.platform.indexOf("Mac") >= 0) { + let menulistlabel = document.getAnonymousElementByAttribute(menulist, "class", "menulist-label"); + let mitemlabel = document.getAnonymousElementByAttribute(menulist.selectedItem, "class", "menu-iconic-text"); + + ok(isWithinHalfPixel(menulistlabel.getBoundingClientRect().left, + mitemlabel.getBoundingClientRect().left), + "Labels horizontally aligned for index " + index); + ok(isWithinHalfPixel(menulistlabel.getBoundingClientRect().top, + mitemlabel.getBoundingClientRect().top), + "Labels vertically aligned for index " + index); + } + else { + let marginTop = parseFloat(getComputedStyle(menulist.menupopup).marginTop); + ok(isWithinHalfPixel(menurect.bottom + marginTop, popuprect.top), + "Vertical alignment with no selection for index " + index); + } + + menulist.open = false; +} + +function popupHidden() +{ + if (!menulist.selectedItem) { + SimpleTest.finish(); + } + else { + menulist.selectedItem = menulist.selectedItem.nextSibling; + menulist.open = true; + } +} +]]> +</script> + +<hbox align="center" pack="center" style="margin-top: 100px;"> + <menulist id="menulist" onpopupshown="popupShown();" onpopuphidden="popupHidden();"> + <menupopup> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="Four"/> + <menuitem label="Five"/> + </menupopup> + </menulist> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_mousescroll.xul b/toolkit/content/tests/chrome/test_mousescroll.xul new file mode 100644 index 0000000000..91ccf56831 --- /dev/null +++ b/toolkit/content/tests/chrome/test_mousescroll.xul @@ -0,0 +1,274 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=378028 +--> +<window title="Mozilla Bug 378028" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=378028" + target="_blank">Mozilla Bug 378028</a> + </body> + + <!-- richlistbox currently has no way of giving us a defined number of + rows, so we just choose an arbitrary height limit that should give + us plenty of vertical scrollability --> + <richlistbox id="richlistbox" style="height:50px;"> + <richlistitem id="richlistbox_item0" hidden="true"><label value="Item 0"/></richlistitem> + <richlistitem id="richlistbox_item1"><label value="Item 1"/></richlistitem> + <richlistitem id="richlistbox_item2"><label value="Item 2"/></richlistitem> + <richlistitem id="richlistbox_item3"><label value="Item 3"/></richlistitem> + <richlistitem id="richlistbox_item4"><label value="Item 4"/></richlistitem> + <richlistitem id="richlistbox_item5"><label value="Item 5"/></richlistitem> + <richlistitem id="richlistbox_item6"><label value="Item 6"/></richlistitem> + <richlistitem id="richlistbox_item7"><label value="Item 7"/></richlistitem> + <richlistitem id="richlistbox_item8"><label value="Item 8"/></richlistitem> + </richlistbox> + + <listbox id="listbox" rows="2"> + <listitem id="listbox_item0" label="Item 0" hidden="true"/> + <listitem id="listbox_item1" label="Item 1"/> + <listitem id="listbox_item2" label="Item 2"/> + <listitem id="listbox_item3" label="Item 3"/> + <listitem id="listbox_item4" label="Item 4"/> + <listitem id="listbox_item5" label="Item 5"/> + <listitem id="listbox_item6" label="Item 6"/> + <listitem id="listbox_item7" label="Item 7"/> + <listitem id="listbox_item8" label="Item 8"/> + </listbox> + + <box orient="horizontal"> + <arrowscrollbox id="hscrollbox" clicktoscroll="true" orient="horizontal" + smoothscroll="false" style="max-width:80px;" flex="1"> + <hbox style="width:40px; height:20px; background:black;" hidden="true"/> + <hbox style="width:40px; height:20px; background:white;"/> + <hbox style="width:40px; height:20px; background:black;"/> + <hbox style="width:40px; height:20px; background:white;"/> + <hbox style="width:40px; height:20px; background:black;"/> + <hbox style="width:40px; height:20px; background:white;"/> + <hbox style="width:40px; height:20px; background:black;"/> + <hbox style="width:40px; height:20px; background:white;"/> + <hbox style="width:40px; height:20px; background:black;"/> + </arrowscrollbox> + </box> + + <arrowscrollbox id="vscrollbox" clicktoscroll="true" orient="vertical" + smoothscroll="false" style="max-height:80px;" flex="1"> + <vbox style="width:100px; height:40px; background:black;" hidden="true"/> + <vbox style="width:100px; height:40px; background:white;"/> + <vbox style="width:100px; height:40px; background:black;"/> + <vbox style="width:100px; height:40px; background:white;"/> + <vbox style="width:100px; height:40px; background:black;"/> + <vbox style="width:100px; height:40px; background:white;"/> + <vbox style="width:100px; height:40px; background:black;"/> + <vbox style="width:100px; height:40px; background:white;"/> + <vbox style="width:100px; height:40px; background:black;"/> + <vbox style="width:100px; height:40px; background:white;"/> + <vbox style="width:100px; height:40px; background:black;"/> + </arrowscrollbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Bug 378028 **/ +/* and for Bug 350471 **/ +var smoothScrollPref = "general.smoothScroll"; +SpecialPowers.setBoolPref(smoothScrollPref, false); +SimpleTest.waitForExplicitFinish(); + +const deltaModes = [ + WheelEvent.DOM_DELTA_PIXEL, // 0 + WheelEvent.DOM_DELTA_LINE, // 1 + WheelEvent.DOM_DELTA_PAGE // 2 +]; + +function testListbox(id) +{ + var listbox = document.getElementById(id); + + function helper(aStart, aDelta, aIntDelta, aDeltaMode) + { + listbox.scrollToIndex(aStart); + synthesizeWheel(listbox, 10, 10, + { deltaMode: aDeltaMode, deltaY: aDelta, + lineOrPageDeltaY: aIntDelta }); + var expectedPos = aStart; + if (aIntDelta) { + if (aDeltaMode == WheelEvent.DOM_DELTA_PAGE) { + expectedPos += aIntDelta > 0 ? listbox.getNumberOfVisibleRows() : + -listbox.getNumberOfVisibleRows(); + } else { + expectedPos += aIntDelta; + } + } + is(listbox.getIndexOfFirstVisibleRow(), expectedPos, + "testListbox(" + id + "): vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + // Check that horizontal scrolling has no effect + listbox.scrollToIndex(aStart); + synthesizeWheel(listbox, 10, 10, + { deltaMode: aDeltaMode, deltaX: aDelta, + lineOrPageDeltaX: aIntDelta }); + is(listbox.getIndexOfFirstVisibleRow(), aStart, + "testListbox(" + id + "): horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + } + deltaModes.forEach(function(aDeltaMode) { + let delta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? 5.0 : 0.3; + helper(5, -delta, 0, aDeltaMode); + helper(5, -delta, -1, aDeltaMode); + helper(5, delta, 1, aDeltaMode); + helper(5, delta, 0, aDeltaMode); + }); +} + +function testRichListbox(id, andThen) +{ + var listbox = document.getElementById(id); + var tests = []; + + var winUtils = SpecialPowers.getDOMWindowUtils(window); + winUtils.advanceTimeAndRefresh(100); + + function nextTest() { + var [aStart, aDelta, aIntDelta, aDeltaMode] = tests.shift(); + listbox.scrollToIndex(aStart); + + let event = { + deltaMode: aDeltaMode, + deltaY: aDelta, + lineOrPageDeltaY: aIntDelta + }; + sendWheelAndPaint(listbox, 10, 10, event, function() { + var change = listbox.getIndexOfFirstVisibleRow() - aStart; + var direction = (change > 0) - (change < 0); + var expected = (aDelta > 0) - (aDelta < 0); + is(direction, expected, + "testRichListbox(" + id + "): vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDeltaY " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + // Check that horizontal scrolling has no effect + let event = { + deltaMode: aDeltaMode, + deltaX: aDelta, + lineOrPageDeltaX: aIntDelta + }; + + listbox.scrollToIndex(aStart); + sendWheelAndPaint(listbox, 10, 10, event, function() { + is(listbox.getIndexOfFirstVisibleRow(), aStart, + "testRichListbox(" + id + "): horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDeltaX " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + if (!tests.length) { + winUtils.restoreNormalRefresh(); + andThen(); + return; + } + + nextTest(); + }); + }); + } + + // richlistbox currently uses native XUL scrolling, so the "line" + // amounts don't necessarily correspond 1-to-1 with listbox items. So + // we just check that scrolling up/down scrolls in the right direction. + deltaModes.forEach(function(aDeltaMode) { + let delta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? 32.0 : 2.0; + tests.push([5, -delta, -1, aDeltaMode]); + tests.push([5, -delta, 0, aDeltaMode]); + tests.push([5, delta, 1, aDeltaMode]); + tests.push([5, delta, 0, aDeltaMode]); + }); + + nextTest(); +} + +function testArrowScrollbox(id) +{ + var scrollbox = document.getElementById(id); + var scrollBoxObject = scrollbox.scrollBoxObject; + var orient = scrollbox.getAttribute("orient"); + + function helper(aStart, aDelta, aDeltaMode, aExpected) + { + var lineOrPageDelta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? aDelta / 10 : aDelta; + var orientIsHorizontal = (orient == "horizontal"); + + scrollBoxObject.scrollTo(aStart, aStart); + + for (var i = orientIsHorizontal ? 2 : 0; i >= 0; i--) { + synthesizeWheel(scrollbox, 5, 5, + { deltaMode: aDeltaMode, deltaY: aDelta, + lineOrPageDeltaY: lineOrPageDelta }); + + var pos = orientIsHorizontal ? scrollBoxObject.positionX : + scrollBoxObject.positionY; + + // Note, vertical mouse scrolling is allowed to scroll horizontal + // arrowscrollboxes, because many users have no horizontal mouse scroll + // capability + let expected = !i ? aExpected : aStart; + is(pos, expected, + "testArrowScrollbox(" + id + "): vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + lineOrPageDelta + + " aDeltaMode " + aDeltaMode); + } + + scrollBoxObject.scrollTo(aStart, aStart); + for (var i = orientIsHorizontal ? 2 : 0; i >= 0; i--) { + synthesizeWheel(scrollbox, 5, 5, + { deltaMode: aDeltaMode, deltaX: aDelta, + lineOrPageDeltaX: lineOrPageDelta }); + // horizontal mouse scrolling is never allowed to scroll vertical + // arrowscrollboxes + var pos = orientIsHorizontal ? scrollBoxObject.positionX : + scrollBoxObject.positionY; + let expected = (!i && orientIsHorizontal) ? aExpected : aStart; + is(pos, expected, + "testArrowScrollbox(" + id + "): horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + lineOrPageDelta + + " aDeltaMode " + aDeltaMode); + } + } + + var scrolledWidth = scrollBoxObject.scrolledWidth; + var scrolledHeight = scrollBoxObject.scrolledHeight; + var scrollMaxX = scrolledWidth - scrollBoxObject.width; + var scrollMaxY = scrolledHeight - scrollBoxObject.height; + var scrollMax = orient == "horizontal" ? scrollMaxX : scrollMaxY; + + deltaModes.forEach(function(aDeltaMode) { + helper(50, -1000, aDeltaMode, 0); + helper(50, 1000, aDeltaMode, scrollMax); + helper(50, 0, aDeltaMode, 50); + helper(50, 0, aDeltaMode, 50); + }); +} + +function runTests() +{ + testRichListbox("richlistbox", function() { + testListbox("listbox"); + testArrowScrollbox("hscrollbox"); + testArrowScrollbox("vscrollbox"); + SpecialPowers.clearUserPref(smoothScrollPref); + SimpleTest.finish(); + }); +} + +window.onload = function() { setTimeout(runTests, 0); }; + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_notificationbox.xul b/toolkit/content/tests/chrome/test_notificationbox.xul new file mode 100644 index 0000000000..a99d0824e8 --- /dev/null +++ b/toolkit/content/tests/chrome/test_notificationbox.xul @@ -0,0 +1,522 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for notificationbox + --> +<window title="Notification Box" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <notificationbox id="nb"/> + <menupopup id="menupopup" onpopupshown="this.hidePopup()" onpopuphidden="checkPopupClosed()"> + <menuitem label="One"/> + </menupopup> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var testtag_notificationbox_buttons = [ + { + label: "Button 1", + accesskey: "u", + callback: testtag_notificationbox_buttonpressed, + popup: "menupopup" + } +]; + +var NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +function testtag_notificationbox_buttonpressed(event) +{ +} + +function testtag_notificationbox(nb) +{ + testtag_notificationbox_State(nb, "initial", null, 0); + + SimpleTest.is(nb.notificationsHidden, false, "initial notificationsHidden"); + SimpleTest.is(nb.removeAllNotifications(false), undefined, "initial removeAllNotifications"); + testtag_notificationbox_State(nb, "initial removeAllNotifications", null, 0); + SimpleTest.is(nb.removeAllNotifications(true), undefined, "initial removeAllNotifications immediate"); + testtag_notificationbox_State(nb, "initial removeAllNotifications immediate", null, 0); + + runTimedTests(tests, -1, nb, null); +} + +var notification_last_events = []; +function notification_eventCallback(event) +{ + notification_last_events.push({ actualEvent: event , item: this }); +} + +/** + * For any notifications that have the notification_eventCallback on + * them, we will have recorded instances of those callbacks firing + * and stored them. This checks to see that the expected event types + * are being fired in order, and targeting the right item. + * + * @param {Array<string>} expectedEvents + * The list of event types, in order, that we expect to have been + * fired on the item. + * @param {<xul:notification>} ntf + * The notification we expect the callback to have been fired from. + * @param {string} testName + * The name of the current test, for logging. + */ +function testtag_notification_eventCallback(expectedEvents, ntf, testName) +{ + for (let i = 0; i < expectedEvents; ++i) { + let expected = expectedEvents[i]; + let { actualEvent, item } = notification_last_events[i]; + SimpleTest.is(actualEvent, expected, testName + ": event name"); + SimpleTest.is(item, ntf, testName + ": event item"); + } + notification_last_events = []; +} + +var tests = +[ + { + test: function(nb, ntf) { + // append a new notification + var ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_LOW, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification"); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "append", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_LOW); + + // check the getNotificationWithValue method + var ntf_found = nb.getNotificationWithValue("note"); + SimpleTest.is(ntf, ntf_found, "getNotificationWithValue note"); + + var none_found = nb.getNotificationWithValue("notenone"); + SimpleTest.is(none_found, null, "getNotificationWithValue null"); + return ntf; + } + }, + { + test: function(nb, ntf) { + // check that notifications can be removed properly + nb.removeNotification(ntf); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "removeNotification", null, 0); + + // try removing the notification again to make sure an exception occurs + var exh = false; + try { + nb.removeNotification(ntf); + } catch (ex) { exh = true; } + SimpleTest.is(exh, true, "removeNotification again"); + testtag_notificationbox_State(nb, "removeNotification again", null, 0); + + } + }, + { + test: function(nb, ntf) { + // append a new notification, but now with an event callback + var ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_LOW, + testtag_notificationbox_buttons, + notification_eventCallback); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification with callback"); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "append with callback", ntf, 1); + return ntf; + } + }, + { + test: function(nb, ntf) { + nb.removeNotification(ntf); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "removeNotification with callback", + null, 0); + + testtag_notification_eventCallback(["removed"], ntf, "removeNotification()"); + return ntf; + } + }, + { + test: function(nb, ntf) { + var ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_LOW, + testtag_notificationbox_buttons, + notification_eventCallback); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification with callback"); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "append with callback", ntf, 1); + return ntf; + } + }, + { + test: function(rb, ntf) { + // Dismissing the notification instead of removing it should + // fire a dismissed "event" on the callback, followed by + // a removed "event". + ntf.dismiss(); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "called dismiss()", null, 0); + testtag_notification_eventCallback(["dismissed", "removed"], ntf, + "dismiss()"); + return ntf; + } + }, + { + test: function(nb, ntf) { + // Create a popup to be used by a menu-button. + var doc = nb.ownerDocument; + var menuPopup = doc.createElementNS(NSXUL, "menupopup"); + var menuItem = menuPopup.appendChild(doc.createElementNS(NSXUL, "menuitem")); + menuItem.setAttribute("label", "Menu Item"); + // Append a notification with a button of type 'menu-button'. + ntf = nb.appendNotification( + "Notification", "note", "happy.png", + nb.PRIORITY_WARNING_LOW, + [{ + label: "Button", + type: "menu-button", + popup: menuPopup + }] + ); + + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "append", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_WARNING_LOW); + var button = ntf.querySelector(".notification-button"); + SimpleTest.is(button.type, "menu-button", "Button type should be set"); + var menuPopup = button.getElementsByTagNameNS(NSXUL, "menupopup"); + SimpleTest.is(menuPopup.length, 1, "There should be a menu attached"); + var menuItem = menuPopup[0].firstChild; + SimpleTest.is(menuItem.localName, "menuitem", "There should be a menu item"); + SimpleTest.is(menuItem.getAttribute("label"), "Menu Item", "Label should match"); + // Clean up. + nb.removeNotification(ntf); + + return [1, null]; + } + }, + { + repeat: true, + test: function(nb, arr) { + var idx = arr[0]; + var ntf = arr[1]; + switch (idx) { + case 1: + // append a new notification + ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_LOW, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification"); + + // Test persistence + ntf.persistence++; + + return [idx, ntf]; + case 2: + case 3: + nb.removeTransientNotifications(); + + return [idx, ntf]; + } + }, + result: function(nb, arr) { + var idx = arr[0]; + var ntf = arr[1]; + switch (idx) { + case 1: + testtag_notificationbox_State(nb, "notification added", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_LOW); + SimpleTest.is(ntf.persistence, 1, "persistence is 1"); + + return [++idx, ntf]; + case 2: + testtag_notificationbox_State(nb, "first removeTransientNotifications", ntf, 1); + testtag_notification_State(nb, ntf, "append", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_LOW); + SimpleTest.is(ntf.persistence, 0, "persistence is now 0"); + + return [++idx, ntf]; + case 3: + testtag_notificationbox_State(nb, "second removeTransientNotifications", null, 0); + + this.repeat = false; + } + } + }, + { + test: function(nb, ntf) { + // append another notification + var ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_MEDIUM, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification again"); + return ntf; + }, + result: function(nb, ntf) { + // check that appending a second notification after removing the first one works + testtag_notificationbox_State(nb, "append again", ntf, 1); + testtag_notification_State(nb, ntf, "append again", "Notification", "note", + "happy.png", nb.PRIORITY_INFO_MEDIUM); + return ntf; + } + }, + { + test: function(nb, ntf) { + // check the removeCurrentNotification method + nb.removeCurrentNotification(); + return ntf; + }, + result: function(nb, ntf) { + testtag_notificationbox_State(nb, "removeCurrentNotification", null, 0); + } + }, + { + test: function(nb, ntf) { + var ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_HIGH, testtag_notificationbox_buttons); + return ntf; + }, + result: function(nb, ntf) { + // test the removeAllNotifications method + testtag_notificationbox_State(nb, "append info_high", ntf, 1); + SimpleTest.is(ntf.priority, nb.PRIORITY_INFO_HIGH, + "notification.priority " + nb.PRIORITY_INFO_HIGH); + SimpleTest.is(nb.removeAllNotifications(false), undefined, "removeAllNotifications"); + } + }, + { + test: function(nb, unused) { + // add a number of notifications and check that they are added in order + nb.appendNotification("Four", "4", null, nb.PRIORITY_INFO_HIGH, testtag_notificationbox_buttons); + nb.appendNotification("Seven", "7", null, nb.PRIORITY_WARNING_HIGH, testtag_notificationbox_buttons); + nb.appendNotification("Two", "2", null, nb.PRIORITY_INFO_LOW, null); + nb.appendNotification("Eight", "8", null, nb.PRIORITY_CRITICAL_LOW, null); + nb.appendNotification("Five", "5", null, nb.PRIORITY_WARNING_LOW, null); + nb.appendNotification("Six", "6", null, nb.PRIORITY_WARNING_HIGH, null); + nb.appendNotification("One", "1", null, nb.PRIORITY_INFO_LOW, null); + nb.appendNotification("Nine", "9", null, nb.PRIORITY_CRITICAL_MEDIUM, null); + var ntf = nb.appendNotification("Ten", "10", null, nb.PRIORITY_CRITICAL_HIGH, null); + nb.appendNotification("Three", "3", null, nb.PRIORITY_INFO_MEDIUM, null); + return ntf; + }, + result: function(nb, ntf) { + SimpleTest.is(nb.currentNotification == ntf ? + nb.currentNotification.value : null, "10", "appendNotification order"); + return 1; + } + }, + { + // test closing notifications to make sure that the current notification is still set properly + repeat: true, + test: function(nb, testidx) { + switch (testidx) { + case 1: + nb.getNotificationWithValue("10").close(); + return [1, 9]; + case 2: + nb.removeNotification(nb.getNotificationWithValue("9")); + return [2, 8]; + case 3: + nb.removeCurrentNotification(); + return [3, 7]; + case 4: + nb.getNotificationWithValue("6").close(); + return [4, 7]; + case 5: + nb.removeNotification(nb.getNotificationWithValue("5")); + return [5, 7]; + case 6: + nb.removeCurrentNotification(); + return [6, 4]; + } + }, + result: function(nb, arr) { + // arr is [testindex, expectedvalue] + SimpleTest.is(nb.currentNotification.value, "" + arr[1], "close order " + arr[0]); + SimpleTest.is(nb.allNotifications.length, 10 - arr[0], "close order " + arr[0] + " count"); + if (arr[0] == 6) + this.repeat = false; + return ++arr[0]; + } + }, + { + test: function(nb, ntf) { + var exh = false; + try { + nb.appendNotification("no", "no", "no", 0, null); + } catch (ex) { exh = true; } + SimpleTest.is(exh, true, "appendNotification priority too low"); + + exh = false; + try { + nb.appendNotification("no", "no", "no", 11, null); + } catch (ex) { exh = true; } + SimpleTest.is(exh, true, "appendNotification priority too high"); + + // check that the other priority types work properly + runTimedTests(appendPriorityTests, -1, nb, nb.PRIORITY_WARNING_LOW); + } + } +]; + +var appendPriorityTests = [ + { + test: function(nb, priority) { + var ntf = nb.appendNotification("Notification", "note", "happy.png", + priority, testtag_notificationbox_buttons); + SimpleTest.is(ntf && ntf.localName == "notification", true, "append notification " + priority); + return [ntf, priority]; + }, + result: function(nb, obj) { + SimpleTest.is(obj[0].priority, obj[1], "notification.priority " + obj[1]); + return obj[1]; + } + }, + { + test: function(nb, priority) { + nb.removeCurrentNotification(); + return priority; + }, + result: function(nb, priority) { + if (priority == nb.PRIORITY_CRITICAL_BLOCK) { + let ntf = nb.appendNotification("Notification", "note", "happy.png", + nb.PRIORITY_INFO_LOW, testtag_notificationbox_buttons); + setTimeout(checkPopupTest, 50, nb, ntf); + } + else { + runTimedTests(appendPriorityTests, -1, nb, ++priority); + } + } + } +]; + +function testtag_notificationbox_State(nb, testid, expecteditem, expectedcount) +{ + SimpleTest.is(nb.currentNotification, expecteditem, testid + " currentNotification"); + SimpleTest.is(nb.allNotifications ? nb.allNotifications.length : "no value", + expectedcount, testid + " allNotifications"); +} + +function testtag_notification_State(nb, ntf, testid, label, value, image, priority) +{ + SimpleTest.is(ntf.control, nb, testid + " notification.control"); + SimpleTest.is(ntf.label, label, testid + " notification.label"); + SimpleTest.is(ntf.value, value, testid + " notification.value"); + SimpleTest.is(ntf.image, image, testid + " notification.image"); + SimpleTest.is(ntf.priority, priority, testid + " notification.priority"); + + var type; + switch (priority) { + case nb.PRIORITY_INFO_LOW: + case nb.PRIORITY_INFO_MEDIUM: + case nb.PRIORITY_INFO_HIGH: + type = "info"; + break; + case nb.PRIORITY_WARNING_LOW: + case nb.PRIORITY_WARNING_MEDIUM: + case nb.PRIORITY_WARNING_HIGH: + type = "warning"; + break; + case nb.PRIORITY_CRITICAL_LOW: + case nb.PRIORITY_CRITICAL_MEDIUM: + case nb.PRIORITY_CRITICAL_HIGH: + case nb.PRIORITY_CRITICAL_BLOCK: + type = "critical"; + break; + } + + SimpleTest.is(ntf.type, type, testid + " notification.type"); +} + +function checkPopupTest(nb, ntf) +{ + if (nb._animating) + setTimeout(checkPopupTest, ntf); + else { + var evt = new Event(""); + ntf.dispatchEvent(evt); + evt.target.buttonInfo = testtag_notificationbox_buttons[0]; + ntf._doButtonCommand(evt); + } +} + +function checkPopupClosed() +{ + is(document.popupNode, null, "popupNode null after popup is closed"); + SimpleTest.finish(); +} + +/** + * run one or more tests which perform a test operation, wait for a delay, + * then perform a result operation. + * + * tests - array of objects where each object is : + * { + * test: test function, + * result: result function + * repeat: true to repeat the test + * } + * idx - starting index in tests + * element - element to run tests on + * arg - argument to pass between test functions + * + * If, after executing the result part, the repeat property of the test is + * true, then the test is repeated. If the repeat property is not true, + * continue on to the next test. + * + * The test and result functions take two arguments, the element and the arg. + * The test function may return a value which will passed to the result + * function as its arg. The result function may also return a value which + * will be passed to the next repetition or the next test in the array. + */ +function runTimedTests(tests, idx, element, arg) +{ + if (idx >= 0 && "result" in tests[idx]) + arg = tests[idx].result(element, arg); + + // if not repeating, move on to the next test + if (idx == -1 || !tests[idx].repeat) + idx++; + + if (idx < tests.length) { + var result = tests[idx].test(element, arg); + setTimeout(runTimedTestsWait, 50, tests, idx, element, result); + } +} + +function runTimedTestsWait(tests, idx, element, arg) +{ + // use this secret property to check if the animation is still running. If it + // is, then the notification hasn't fully opened or closed yet + if (element._animating) + setTimeout(runTimedTestsWait, 50, tests, idx, element, arg); + else + runTimedTests(tests, idx, element, arg); +} + +setTimeout(testtag_notificationbox, 0, document.getElementById('nb')); +]]> +</script> + +</window> + diff --git a/toolkit/content/tests/chrome/test_panel.xul b/toolkit/content/tests/chrome/test_panel.xul new file mode 100644 index 0000000000..3b2188d9e7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Panel Tests" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_panel.xul", "_blank", "chrome,left=200,top=200,width=200,height=200"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_panel_focus.xul b/toolkit/content/tests/chrome/test_panel_focus.xul new file mode 100644 index 0000000000..e18f28ca8f --- /dev/null +++ b/toolkit/content/tests/chrome/test_panel_focus.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Panel Focus Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +// use a chrome window for this test as the focus in content windows can be +// adjusted by the current selection position + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + // move the mouse so any tooltips that might be open go away, otherwise this + // test can fail on Mac + synthesizeMouse(document.documentElement, 1, 1, { type: "mousemove" }); + + window.open("window_panel_focus.xul", "_blank", "chrome,width=600,height=600"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_panelfrommenu.xul b/toolkit/content/tests/chrome/test_panelfrommenu.xul new file mode 100644 index 0000000000..72e3965163 --- /dev/null +++ b/toolkit/content/tests/chrome/test_panelfrommenu.xul @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Open panel from menuitem" + onload="setTimeout(runTests, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test does the following: + 1. Opens the menu, causing the popupshown event to fire, which will call menuOpened. + 2. Keyboard events are fired to cause the first item on the menu to be executed. + 3. The command event handler for the first menuitem opens the panel. + 4. As a menuitem was executed, the menu will roll up, hiding it. + 5. The popuphidden event for the menu calls menuClosed which tests the popup states. + 6. The panelOpened function tests the popup states again and hides the popup. + 7. Once the panel's popuphidden event fires, tests are performed to see if + panels inside buttons and toolbarbuttons work. Each is opened and the closed. + --> + +<menu id="menu" onpopupshown="menuOpened()" onpopuphidden="menuClosed();"> + <menupopup> + <menuitem id="i1" label="One" oncommand="$('panel').openPopup($('menu'), 'after_start');"/> + <menuitem id="i2" label="Two"/> + </menupopup> +</menu> + +<panel id="hiddenpanel" hidden="true"/> + +<panel id="panel" onpopupshown="panelOpened()" + onpopuphidden="$('button').focus(); $('button').open = true"> + <textbox/> +</panel> + +<button id="button" type="panel" label="Button"> + <panel onpopupshown="panelOnButtonOpened(this)" + onpopuphidden="$('tbutton').open = true;"> + <button label="OK" oncommand="this.parentNode.parentNode.open = false"/> + </panel> +</button> + +<toolbarbutton id="tbutton" type="panel" label="Toolbarbutton"> + <panel onpopupshown="panelOnToolbarbuttonOpened(this)" + onpopuphidden="SimpleTest.finish()"> + <textbox/> + </panel> +</toolbarbutton> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + is($("hiddenpanel").state, "closed", "hidden popup is closed"); + + var menu = $("menu"); + menu.open = true; +} + +function menuOpened() +{ + synthesizeKey("VK_DOWN", { }); + synthesizeKey("VK_RETURN", { }); +} + +function menuClosed() +{ + // the panel will be open at this point, but the popupshown event + // still needs to fire + is($("panel").state, "showing", "panel is open after menu hide"); + is($("menu").firstChild.state, "closed", "menu is closed after menu hide"); +} + +function panelOpened() +{ + is($("panel").state, "open", "panel is open"); + is($("menu").firstChild.state, "closed", "menu is closed"); + $("panel").hidePopup(); +} + +function panelOnButtonOpened(panel) +{ + is(panel.state, 'open', 'button panel is open'); + is(document.activeElement, document.documentElement, "focus blurred on panel from button open"); + synthesizeKey("VK_DOWN", { }); + is(document.activeElement, document.documentElement, "focus not modified on cursor down from button"); + panel.firstChild.doCommand() +} + +function panelOnToolbarbuttonOpened(panel) +{ + is(panel.state, 'open', 'toolbarbutton panel is open'); + is(document.activeElement, document.documentElement, "focus blurred on panel from toolbarbutton open"); + panel.firstChild.focus(); + synthesizeKey("VK_DOWN", { }); + is(document.activeElement, panel.firstChild.inputField, "focus not modified on cursor down from toolbarbutton"); + panel.parentNode.open = false; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_anchor.xul b/toolkit/content/tests/chrome/test_popup_anchor.xul new file mode 100644 index 0000000000..5839c52a37 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_anchor.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Anchor Tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_popup_anchor.xul", "_blank", "chrome,width=600,height=600"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_anchoratrect.xul b/toolkit/content/tests/chrome/test_popup_anchoratrect.xul new file mode 100644 index 0000000000..c12e225026 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_anchoratrect.xul @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Button Popup Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_popup_anchoratrect.xul", "_blank", "chrome,width=200,height=200"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_attribute.xul b/toolkit/content/tests/chrome/test_popup_attribute.xul new file mode 100644 index 0000000000..2a256078d5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_attribute.xul @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Attribute Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_popup_attribute.xul", "_blank", "width=600,height=700"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_button.xul b/toolkit/content/tests/chrome/test_popup_button.xul new file mode 100644 index 0000000000..3803e465f5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_button.xul @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu Button Popup Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_popup_button.xul", "_blank", "width=700,height=700"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_coords.xul b/toolkit/content/tests/chrome/test_popup_coords.xul new file mode 100644 index 0000000000..4597b5cc03 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_coords.xul @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Coordinate Tests" + onload="setTimeout(openThePopup, 0, 'outer');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<deck style="margin-top: 5px; padding-top: 5px;"> + <label id="outer" popup="outerpopup" value="Popup"/> +</deck> + +<panel id="outerpopup" + onpopupshowing="popupShowingEventOccurred(event);" + onpopupshown="eventOccurred(event); openThePopup('inner')" + onpopuphiding="eventOccurred(event);" + onpopuphidden="eventOccurred(event); SimpleTest.finish();"> + <button id="item1" label="First"/> + <label id="inner" value="Second" popup="innerpopup"/> + <button id="item2" label="Third"/> +</panel> + +<menupopup id="innerpopup" + onpopupshowing="popupShowingEventOccurred(event);" + onpopupshown="eventOccurred(event); event.target.hidePopup();" + onpopuphiding="eventOccurred(event);" + onpopuphidden="eventOccurred(event); document.getElementById('outerpopup').hidePopup();"> + <menuitem id="inner1" label="Inner First"/> + <menuitem id="inner2" label="Inner Second"/> +</menupopup> + +<script> +SimpleTest.waitForExplicitFinish(); + +function openThePopup(id) +{ + if (id == "inner") + document.getElementById("item1").focus(); + + var trigger = document.getElementById(id); + synthesizeMouse(trigger, 4, 5, { }); +} + +function eventOccurred(event) +{ + var testname = event.type + " on " + event.target.id + " "; + ok(event instanceof MouseEvent, testname + "is a mouse event"); + is(event.clientX, 0, testname + "clientX"); + is(event.clientY, 0, testname + "clientY"); + is(event.rangeParent, null, testname + "rangeParent"); + is(event.rangeOffset, 0, testname + "rangeOffset"); +} + +function popupShowingEventOccurred(event) +{ + // the popupshowing event should have the event coordinates and + // range position filled in. + var testname = "popupshowing on " + event.target.id + " "; + ok(event instanceof MouseEvent, testname + "is a mouse event"); + + var trigger = document.getElementById(event.target.id == "outerpopup" ? "outer" : "inner"); + var rect = trigger.getBoundingClientRect(); + is(event.clientX, Math.round(rect.left + 4), testname + "clientX"); + is(event.clientY, Math.round(rect.top + 5), testname + "clientY"); + // rangeOffset should be just after the trigger element. As rangeOffset + // considers the zeroth position to be before the first element, the value + // should be one higher than its index within its parent. + is(event.rangeParent, trigger.parentNode, testname + "rangeParent"); + is(event.rangeOffset, Array.indexOf(trigger.parentNode.childNodes, trigger) + 1, testname + "rangeOffset"); + + var popuprect = event.target.getBoundingClientRect(); + is(Math.round(popuprect.left), Math.round(rect.left + 4), "popup left"); + is(Math.round(popuprect.top), Math.round(rect.top + 5), "popup top"); + ok(popuprect.width > 0, "popup width"); + ok(popuprect.height > 0, "popup height"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_keys.xul b/toolkit/content/tests/chrome/test_popup_keys.xul new file mode 100644 index 0000000000..37135af57e --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_keys.xul @@ -0,0 +1,148 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menu ignorekeys Test" + onkeydown="keyDown()" onkeypress="gKeyPressCount++; event.stopPropagation(); event.preventDefault();" + onload="runTests();" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks that the ignorekeys attribute can be used on a menu to + disable key navigation. The test is performed twice by opening the menu, + simulating a cursor down key, and closing the popup. When keys are enabled, + the first item on the menu should be highlighted, otherwise the first item + should not be highlighted. + --> + +<menupopup id="popup"> + <menuitem id="i1" label="One" onDOMAttrModified="attrModified(event)"/> + <menuitem id="i2" label="Two"/> + <menuitem id="i3" label="Three"/> + <menuitem id="i4" label="Four"/> +</menupopup> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gIgnoreKeys = false; +var gIgnoreAttrChange = false; +var gKeyPressCount = 0; + +let {Task} = Components.utils.import("resource://gre/modules/Task.jsm", {}); + +function waitForEvent(target, eventName) { + return new Promise(resolve => { + target.addEventListener(eventName, function eventOccurred(event) { + target.removeEventListener(eventName, eventOccurred, false); + resolve(); + }, false); + }); +} + +function runTests() +{ + Task.async(function* () { + var popup = $("popup"); + popup.enableKeyboardNavigator(false); + is(popup.getAttribute("ignorekeys"), "true", "keys disabled"); + popup.enableKeyboardNavigator(true); + is(popup.hasAttribute("ignorekeys"), false, "keys enabled"); + + let popupShownPromise = waitForEvent(popup, "popupshown"); + popup.openPopup(null, "after_start"); + yield popupShownPromise; + + let popupHiddenPromise = waitForEvent(popup, "popuphidden"); + synthesizeKey("VK_DOWN", { }); + yield popupHiddenPromise; + + is(gKeyPressCount, 0, "keypresses with ignorekeys='false'"); + + gIgnoreKeys = true; + popup.setAttribute("ignorekeys", "true"); + // clear this first to avoid confusion + gIgnoreAttrChange = true; + $("i1").removeAttribute("_moz-menuactive") + gIgnoreAttrChange = false; + + popupShownPromise = waitForEvent(popup, "popupshown"); + popup.openPopup(null, "after_start"); + yield popupShownPromise; + + synthesizeKey("VK_DOWN", { }); + + yield new Promise(resolve => setTimeout(() => resolve(), 1000)); + popupHiddenPromise = waitForEvent(popup, "popuphidden"); + popup.hidePopup(); + yield popupHiddenPromise; + + is(gKeyPressCount, 1, "keypresses with ignorekeys='true'"); + + popup.setAttribute("ignorekeys", "shortcuts"); + // clear this first to avoid confusion + gIgnoreAttrChange = true; + $("i1").removeAttribute("_moz-menuactive") + gIgnoreAttrChange = false; + + popupShownPromise = waitForEvent(popup, "popupshown"); + popup.openPopup(null, "after_start"); + yield popupShownPromise; + + // When ignorekeys="shortcuts", T should be handled but accel+T should propagate. + synthesizeKey("t", { }); + is(gKeyPressCount, 1, "keypresses after t pressed with ignorekeys='shortcuts'"); + + synthesizeKey("t", { accelKey: true }); + is(gKeyPressCount, 2, "keypresses after accel+t pressed with ignorekeys='shortcuts'"); + + popupHiddenPromise = waitForEvent(popup, "popuphidden"); + popup.hidePopup(); + yield popupHiddenPromise; + + SimpleTest.finish(); + })(); +} + +function attrModified(event) +{ + if (gIgnoreAttrChange || event.attrName != "_moz-menuactive") + return; + + // the attribute should not be changed when ignorekeys is enabled + if (gIgnoreKeys) { + ok(false, "move key with keys disabled"); + } + else { + is($("i1").getAttribute("_moz-menuactive"), "true", "move key with keys enabled"); + $("popup").hidePopup(); + } +} + +function keyDown() +{ + // when keys are enabled, the menu should have stopped propagation of the + // event, so a bubbling listener for a keydown event should only occur + // when keys are disabled. + ok(gIgnoreKeys, "key listener fired with keys " + + (gIgnoreKeys ? "disabled" : "enabled")); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_moveToAnchor.xul b/toolkit/content/tests/chrome/test_popup_moveToAnchor.xul new file mode 100644 index 0000000000..344002fdf6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_moveToAnchor.xul @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<vbox align="start"> + <button id="button1" label="Button 1" style="margin-top: 50px;"/> + <button id="button2" label="Button 2" style="margin-top: 60px;"/> +</vbox> + +<menupopup id="popup" onpopupshown="popupshown()" onpopuphidden="SimpleTest.finish()"> + <menuitem label="One"/> + <menuitem label="Two"/> +</menupopup> + +<script> +SimpleTest.waitForExplicitFinish(); + +function runTest(id) +{ + $("popup").openPopup($("button1"), "after_start"); +} + +function popupshown() +{ + var popup = $("popup"); + var popupheight = popup.getBoundingClientRect().height; + var button1rect = $("button1").getBoundingClientRect(); + var button2rect = $("button2").getBoundingClientRect(); + + checkCoords(popup, button1rect.left, button1rect.bottom, "initial"); + + popup.moveToAnchor($("button1"), "after_start", 0, 8); + checkCoords(popup, button1rect.left, button1rect.bottom + 8, "move anchor top + 8"); + + popup.moveToAnchor($("button1"), "after_start", 6, -10); + checkCoords(popup, button1rect.left + 6, button1rect.bottom - 10, "move anchor left + 6, top - 10"); + + popup.moveToAnchor($("button1"), "before_start", -2, 0); + checkCoords(popup, button1rect.left - 2, button1rect.top - popupheight, "move anchor before_start"); + + popup.moveToAnchor($("button2"), "before_start"); + checkCoords(popup, button2rect.left, button2rect.top - popupheight, "move button2"); + + popup.moveToAnchor($("button1"), "end_before"); + checkCoords(popup, button1rect.right, button1rect.top, "move anchor end_before"); + + popup.moveToAnchor($("button2"), "after_start", 5, 4); + checkCoords(popup, button2rect.left + 5, button2rect.bottom + 4, "move button2 left + 5, top + 4"); + + popup.moveTo($("button1").boxObject.screenX + 10, $("button1").boxObject.screenY + 12); + checkCoords(popup, button1rect.left + 10, button1rect.top + 12, "move to button1 screen with offset"); + + popup.moveToAnchor($("button1"), "after_start", 1, 2); + checkCoords(popup, button1rect.left + 1, button1rect.bottom + 2, "move button2 after screen"); + + popup.hidePopup(); +} + +function checkCoords(popup, expectedx, expectedy, testid) +{ + var rect = popup.getBoundingClientRect(); + is(Math.round(rect.left), Math.round(expectedx), testid + " left"); + is(Math.round(rect.top), Math.round(expectedy), testid + " top"); +} + +SimpleTest.waitForFocus(runTest); + +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_preventdefault.xul b/toolkit/content/tests/chrome/test_popup_preventdefault.xul new file mode 100644 index 0000000000..7de5dc3be1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_preventdefault.xul @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Prevent Default Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<!-- + This tests checks that preventDefault can be called on a popupshowing + event and that preventDefault has no effect for the popuphiding event. + --> + +<script> +SimpleTest.waitForExplicitFinish(); + +var gBlockShowing = true; +var gShownNotAllowed = true; + +function runTest() +{ + document.getElementById("menu").open = true; +} + +function popupShowing(event) +{ + if (gBlockShowing) { + event.preventDefault(); + gBlockShowing = false; + setTimeout(function() { + gShownNotAllowed = false; + document.getElementById("menu").open = true; + }, 3000, true); + } +} + +function popupShown() +{ + ok(!gShownNotAllowed, "popupshowing preventDefault"); + document.getElementById("menu").open = false; +} + +function popupHiding(event) +{ + // since this is a content test, preventDefault should have no effect + event.preventDefault(); +} + +function popupHidden() +{ + ok(true, "popuphiding preventDefault not allowed"); + SimpleTest.finish(); +} +</script> + +<button id="menu" type="menu" label="Menu"> + <menupopup onpopupshowing="popupShowing(event);" + onpopupshown="popupShown();" + onpopuphiding="popupHiding(event);" + onpopuphidden="popupHidden();"> + <menuitem label="Item"/> + </menupopup> +</button> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_preventdefault_chrome.xul b/toolkit/content/tests/chrome/test_popup_preventdefault_chrome.xul new file mode 100644 index 0000000000..46f14cd6a5 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_preventdefault_chrome.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Attribute Tests" + onload="setTimeout(runTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_popup_preventdefault_chrome.xul", "_blank", "chrome,width=600,height=600"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_recreate.xul b/toolkit/content/tests/chrome/test_popup_recreate.xul new file mode 100644 index 0000000000..14822acbd0 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_recreate.xul @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Recreate Test" + onload="setTimeout(init, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This is a test for bug 388361. + + This test checks that a menulist's popup is properly created and sized when + the popup node is removed and another added in its place. + + --> + +<script> +<![CDATA[ +SimpleTest.waitForExplicitFinish(); + +var gState = "before"; + +function init() +{ + document.getElementById("menulist").open = true; +} + +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +function recreate() +{ + if (gState == "before") { + var element = document.getElementById("menulist"); + while (element.hasChildNodes()) + element.removeChild(element.firstChild); + element.appendItem("Cat"); + gState = "after"; + document.getElementById("menulist").open = true; + } + else { + SimpleTest.finish(); + } +} + +function checkSize() +{ + var menulist = document.getElementById("menulist"); + var menurect = menulist.getBoundingClientRect(); + var popuprect = menulist.menupopup.getBoundingClientRect(); + + let marginLeft = parseFloat(getComputedStyle(menulist.menupopup).marginLeft); + ok(isWithinHalfPixel(menurect.left + marginLeft, popuprect.left), "left position " + gState); + ok(isWithinHalfPixel(menurect.right + marginLeft, popuprect.right), "right position " + gState); + ok(Math.round(popuprect.right) - Math.round(popuprect.left) > 0, "height " + gState) + document.getElementById("menulist").open = false; +} +]]> +</script> + +<hbox align="center" pack="center"> + <menulist id="menulist" onpopupshown="checkSize();" onpopuphidden="recreate();"> + <menupopup position="after_start"> + <menuitem label="Cat"/> + </menupopup> + </menulist> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_scaled.xul b/toolkit/content/tests/chrome/test_popup_scaled.xul new file mode 100644 index 0000000000..6bbf6c6533 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_scaled.xul @@ -0,0 +1,105 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popups in Scaled Content" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- This test checks that the position is correct in two cases: + - a popup anchored at an element in a scaled document + - a popup opened at a screen coordinate in a scaled window + --> + +<iframe id="frame" width="60" height="140" + src="data:text/html,<html><body><input size='4' id='one'><input size='4' id='two'></body></html>"/> + +<menupopup id="popup" onpopupshown="shown()" onpopuphidden="nextTest()"> + <menuitem label="One"/> +</menupopup> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var screenTest = false; +var screenx = -1, screeny = -1; + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + setScale($("frame").contentWindow, 2); + + var anchor = $("frame").contentDocument.getElementById("two"); + anchor.getBoundingClientRect(); // flush to update display after scale change + $("popup").openPopup(anchor, "after_start"); +} + +function setScale(win, scale) +{ + var wn = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation); + var shell = wn.QueryInterface(Components.interfaces.nsIDocShell); + var docViewer = shell.contentViewer; + docViewer.fullZoom = scale; +} + +function shown() +{ + if (screenTest) { + var box = $("popup").boxObject; + is(box.screenX, screenx, "screen left position"); + is(box.screenY, screeny, "screen top position"); + } + else { + var anchor = $("frame").contentDocument.getElementById("two"); + + is(Math.round(anchor.getBoundingClientRect().left * 2), + Math.round($("popup").getBoundingClientRect().left), "anchored left position"); + is(Math.round(anchor.getBoundingClientRect().bottom * 2), + Math.round($("popup").getBoundingClientRect().top), "anchored top position"); + } + + $("popup").hidePopup(); +} + +function nextTest() +{ + if (screenTest) { + setScale(window, 1); + SimpleTest.finish(); + } + else { + screenTest = true; + var box = document.documentElement.boxObject; + + // - the iframe is at 4×, but out here css pixels are only 2× device pixels + // - the popup manager rounds off (or truncates) the coordinates to + // integers, so ensure we pass in even numbers to openPopupAtScreen + screenx = (x = even(box.screenX + 120))/2; + screeny = (y = even(box.screenY + 120))/2; + setScale(window, 2); + $("popup").openPopupAtScreen(x, y); + } +} + +function even(n) +{ + return (n % 2) ? n+1 : n; +} +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popup_tree.xul b/toolkit/content/tests/chrome/test_popup_tree.xul new file mode 100644 index 0000000000..779f13e684 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popup_tree.xul @@ -0,0 +1,72 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tree in Popup Test" + onload="setTimeout(runTests, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<panel id="panel" onpopupshown="treeClick()" onpopuphidden="SimpleTest.finish()"> + <tree id="tree" width="350" rows="5"> + <treecols> + <treecol id="name" label="Name" flex="1"/> + <treecol id="address" label="Street" flex="1"/> + </treecols> + <treechildren id="treechildren"> + <treeitem> + <treerow> + <treecell label="Justin Thyme"/> + <treecell label="800 Bay Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary Goround"/> + <treecell label="47 University Avenue"/> + </treerow> + </treeitem> + </treechildren> + </tree> +</panel> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + $("panel").openPopup(null, "overlap", 2, 2); +} + +function treeClick() +{ + var tree = $("tree"); + is(tree.currentIndex, -1, "selectedIndex before click"); + synthesizeMouseExpectEvent($("treechildren"), 2, 2, { }, $("treechildren"), "click", ""); + is(tree.currentIndex, 0, "selectedIndex after click"); + + var rect = tree.treeBoxObject.getCoordsForCellItem(1, tree.columns.address, ""); + synthesizeMouseExpectEvent($("treechildren"), rect.x, rect.y + 2, + { }, $("treechildren"), "click", ""); + is(tree.currentIndex, 1, "selectedIndex after second click " + rect.x + "," + rect.y); + + $("panel").hidePopup(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popuphidden.xul b/toolkit/content/tests/chrome/test_popuphidden.xul new file mode 100644 index 0000000000..4c344b3d22 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popuphidden.xul @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Hidden Popup Test" + onload="setTimeout(runTests, 0, $('popup'));" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<menupopup id="popup" hidden="true" onpopupshown="ok(true, 'popupshown'); this.hidePopup()" + onpopuphidden="$('popup-hideonshow').openPopup(null, 'after_start')"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> +</menupopup> + +<menupopup id="popup-hideonshow" onpopupshowing="hidePopupWhileShowing(this)" + onpopupshown="ok(false, 'popupshown when hidden')"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> +</menupopup> + +<button id="button" type="menu" label="Menu" onDOMAttrModified="checkEndTest(event)"> + <menupopup id="popupinbutton" hidden="true" + onpopupshown="ok(true, 'popupshown'); ok($('button').open, 'open'); this.hidden = true;"> + <menuitem id="i1" label="One"/> + <menuitem id="i2" label="Two"/> + </menupopup> +</button> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTests(popup) +{ + popup.hidden = false; + popup.openPopup(null, "after_start"); +} + +function hidePopupWhileShowing(popup) +{ + popup.hidden = true; + popup.clientWidth; // flush layout + is(popup.state, 'closed', 'popupshowing hidden'); + SimpleTest.executeSoon(() => runTests($('popupinbutton'))); +} + +function checkEndTest(event) +{ + var button = $("button"); + if (event.originalTarget != button || event.attrName != 'open' || event.attrChange != event.REMOVAL) + return; + + ok($("popupinbutton").hidden, "popup hidden"); + is($("popupinbutton").state, "closed", "popup state"); + ok(!button.open, "not open after hidden"); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popupincontent.xul b/toolkit/content/tests/chrome/test_popupincontent.xul new file mode 100644 index 0000000000..dafcd09e5a --- /dev/null +++ b/toolkit/content/tests/chrome/test_popupincontent.xul @@ -0,0 +1,131 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup in Content Positioning Tests" + onload="setTimeout(nextTest, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + This test checks that popups in content areas don't extend past the content area. + --> + +<hbox> + <spacer width="100"/> + <menu id="menu" label="Menu"> + <menupopup style="margin:10px;" id="popup" onpopupshown="popupShown()" onpopuphidden="nextTest()"> + <menuitem label="One"/> + <menuitem label="Two"/> + <menuitem label="Three"/> + <menuitem label="A final longer label that is actually quite long. Very long indeed."/> + </menupopup> + </menu> +</hbox> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var step = ""; +var originalHeight = -1; + +function nextTest() +{ + // there are five tests here: + // openPopupAtScreen - checks that opening a popup using openPopupAtScreen + // constrains the popup to the content area + // left and top - check with the left and top attributes set + // open near bottom - open the menu near the bottom of the window + // large menu - try with a menu that is very large and should be scaled + // shorter menu again - try with a menu that is shorter again. It should have + // the same height as the 'left and top' test + var popup = $("popup"); + var menu = $("menu"); + switch (step) { + case "": + step = "openPopupAtScreen"; + popup.openPopupAtScreen(1000, 1200); + break; + case "openPopupAtScreen": + step = "left and top"; + popup.setAttribute("left", "800"); + popup.setAttribute("top", "2900"); + synthesizeMouse(menu, 2, 2, { }); + break; + case "left and top": + step = "open near bottom"; + // request that the menu be opened with a target point near the bottom of the window, + // so that the menu's top margin will push it completely outside the window. + var bo = document.documentElement.boxObject; + popup.setAttribute("top", bo.screenY + window.innerHeight - 5); + synthesizeMouse(menu, 2, 2, { }); + break; + case "open near bottom": + step = "large menu"; + popup.removeAttribute("left"); + popup.removeAttribute("top"); + for (var i = 0; i < 80; i++) + menu.appendItem("Test", ""); + synthesizeMouse(menu, 2, 2, { }); + break; + case "large menu": + step = "shorter menu again"; + for (var i = 0; i < 80; i++) + menu.removeItemAt(menu.itemCount - 1); + synthesizeMouse(menu, 2, 2, { }); + break; + case "shorter menu again": + SimpleTest.finish(); + break; + } +} + +function popupShown() +{ + var windowrect = document.documentElement.getBoundingClientRect(); + var popuprect = $("popup").getBoundingClientRect(); + + // subtract one off the edge due to a rounding issue + ok(popuprect.left >= windowrect.left, step + " left"); + ok(popuprect.right - 1 <= windowrect.right, step + " right"); + + if (step == "left and top") { + originalHeight = popuprect.bottom - popuprect.top; + } + else if (step == "open near bottom") { + // check that the menu flipped up so it's above our requested point + ok(popuprect.bottom - 1 <= windowrect.bottom - 5, step + " bottom"); + } + else if (step == "large menu") { + // add 10 to account for the margin + is(popuprect.top, $("menu").getBoundingClientRect().bottom + 10, step + " top"); + ok(popuprect.bottom == windowrect.bottom || + popuprect.bottom - 1 == windowrect.bottom, step + " bottom"); + } + else { + ok(popuprect.top >= windowrect.top, step + " top"); + ok(popuprect.bottom - 1 <= windowrect.bottom, step + " bottom"); + if (step == "shorter menu again") + is(popuprect.bottom - popuprect.top, originalHeight, step + " height shortened"); + } + + $("menu").open = false; +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popupremoving.xul b/toolkit/content/tests/chrome/test_popupremoving.xul new file mode 100644 index 0000000000..f795590f52 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popupremoving.xul @@ -0,0 +1,165 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Removing Tests" + onload="setTimeout(nextTest, 0)" + onDOMAttrModified="modified(event)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<!-- + This test checks that popup elements can be removed in various ways without + crashing. It tests two situations, one with menus that are 'separate', and + one with menus that are 'nested'. In each case, there are four levels of menu. + + The nextTest function starts the process by opening the first menu. A set of + popupshown event listeners are used to open the next menu until all four are + showing. This last one calls removePopup to remove the menu node from the + tree. This should hide the popups as they are no longer in a document. + + A mutation listener is triggered when the fourth menu closes by having its + open attribute cleared. This listener hides the third popup which causes + its frame to be removed. Naturally, we want to ensure that this doesn't + crash when the third menu is removed. + --> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<hbox> + +<menu id="nestedmenu1" label="1"> + <menupopup id="nestedpopup1" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu2" label="2"> + <menupopup id="nestedpopup2" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu3" label="3"> + <menupopup id="nestedpopup3" onpopupshown="if (event.target == this) this.firstChild.open = true"> + <menu id="nestedmenu4" label="4" onpopupshown="removePopups()"> + <menupopup id="nestedpopup4"> + <menuitem label="Nested 1"/> + <menuitem label="Nested 2"/> + <menuitem label="Nested 3"/> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> + </menu> + </menupopup> +</menu> + +<menu id="separatemenu1" label="1"> + <menupopup id="separatepopup1" onpopupshown="$('separatemenu2').open = true"> + <menuitem label="L1 One"/> + <menuitem label="L1 Two"/> + <menuitem label="L1 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu2" label="2"> + <menupopup id="separatepopup2" onpopupshown="$('separatemenu3').open = true" + onpopuphidden="popup2Hidden()"> + <menuitem label="L2 One"/> + <menuitem label="L2 Two"/> + <menuitem label="L2 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu3" label="3" onpopupshown="$('separatemenu4').open = true"> + <menupopup id="separatepopup3"> + <menuitem label="L3 One"/> + <menuitem label="L3 Two"/> + <menuitem label="L3 Three"/> + </menupopup> +</menu> + +<menu id="separatemenu4" label="4" onpopupshown="removePopups()" + onpopuphidden="$('separatemenu2').open = false"> + <menupopup id="separatepopup3"> + <menuitem label="L4 One"/> + <menuitem label="L4 Two"/> + <menuitem label="L4 Three"/> + </menupopup> +</menu> + +</hbox> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gKey = ""; +gTriggerMutation = null; +gChangeMutation = null; + +function nextTest() +{ + if (gKey == "") { + gKey = "separate"; + } + else if (gKey == "separate") { + gKey = "nested"; + } + else { + SimpleTest.finish(); + return; + } + + $(gKey + "menu1").open = true; +} + +function modified(event) +{ + // use this mutation listener to hide the third popup, destroying its frame. + // It gets triggered when the open attribute is cleared on the fourth menu. + + if (event.target == gTriggerMutation && + event.attrName == "open") { + gChangeMutation.hidden = true; + // force a layout flush + document.documentElement.boxObject.width; + gTriggerMutation = null; + gChangeMutation = null; + } +} + +function removePopups() +{ + var menu2 = $(gKey + "menu2"); + var menu3 = $(gKey + "menu3"); + is(menu2.getAttribute("open"), "true", gKey + " menu 2 open before"); + is(menu3.getAttribute("open"), "true", gKey + " menu 3 open before"); + + gTriggerMutation = menu3; + gChangeMutation = $(gKey + "menu4"); + var menu = $(gKey + "menu1"); + menu.parentNode.removeChild(menu); + + if (gKey == "nested") { + // the 'separate' test checks this during the popup2 hidden event handler + is(menu2.hasAttribute("open"), false, gKey + " menu 2 open after"); + is(menu3.hasAttribute("open"), false, gKey + " menu 3 open after"); + nextTest(); + } +} + +function popup2Hidden() +{ + is($(gKey + "menu2").hasAttribute("open"), false, gKey + " menu 2 open after"); + nextTest(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_popupremoving_frame.xul b/toolkit/content/tests/chrome/test_popupremoving_frame.xul new file mode 100644 index 0000000000..dec73c7f70 --- /dev/null +++ b/toolkit/content/tests/chrome/test_popupremoving_frame.xul @@ -0,0 +1,80 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Unload Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<!-- + This test checks that popup elements are removed when the document is changed. + --> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<iframe id="frame" width="300" height="150" src="frame_popupremoving_frame.xul"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gMenus = []; + +function popupsOpened() +{ + var framedoc = $("frame").contentDocument; + framedoc.addEventListener("DOMAttrModified", modified, false); + + // this is the order in which the menus should be hidden (reverse of the + // order they were opened in). The second menu is removed during the + // mutation listener, so gets the event afterwards. + gMenus.push(framedoc.getElementById("nestedmenu4")); + gMenus.push(framedoc.getElementById("nestedmenu2")); + gMenus.push(framedoc.getElementById("nestedmenu3")); + gMenus.push(framedoc.getElementById("nestedmenu1")); + gMenus.push(framedoc.getElementById("separatemenu4")); + gMenus.push(framedoc.getElementById("separatemenu2")); + gMenus.push(framedoc.getElementById("separatemenu3")); + gMenus.push(framedoc.getElementById("separatemenu1")); + + framedoc.location = "about:blank"; +} + +function modified(event) +{ + if (event.attrName != "open") + return; + + var framedoc = $("frame").contentDocument; + + var tohide = null; + if (event.target.id == "separatemenu3") + tohide = framedoc.getElementById("separatemenu2"); + else if (event.target.id == "nestedmenu3") + tohide = framedoc.getElementById("nestedmenu2"); + + if (tohide) { + tohide.hidden = true; + // force a layout flush + $("frame").contentDocument.documentElement.boxObject.width; + } + + is(event.target, gMenus.shift(), event.target.id + " hidden"); + if (gMenus.length == 0) + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_position.xul b/toolkit/content/tests/chrome/test_position.xul new file mode 100644 index 0000000000..695c1bf22c --- /dev/null +++ b/toolkit/content/tests/chrome/test_position.xul @@ -0,0 +1,136 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for positioning + --> +<window title="position" width="500" height="600" + onload="setTimeout(runTest, 0);" + style="margin: 0; border: 0; padding; 0;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + +<hbox id="box1"> + <button label="0" width="100" height="40" style="margin: 3px;"/> +</hbox> +<scrollbox id="box2" orient="vertical" align="start" width="200" height="50" + style="overflow: hidden; margin-left: 2px; padding: 1px;"> + <deck> + <scrollbox id="box3" orient="vertical" align="start" height="100" + style="overflow: scroll; margin: 1px; padding: 0;"> + <vbox id="innerscroll" width="200" align="start"> + <button id="button1" label="1" width="90" maxwidth="100" + minheight="25" height="35" maxheight="50" + style="min-width: 80px; margin: 5px; border: 4px; padding: 7px; + -moz-appearance: none;"/> + <menu id="menu"> + <menupopup id="popup" style="-moz-appearance: none; margin:0; border: 0; padding: 0;" + onpopupshown="menuOpened()" + onpopuphidden="if (event.target == this) SimpleTest.finish()"> + <menuitem label="One"/> + <menu id="submenu" label="Three"> + <menupopup id="subpopup" style="-moz-appearance: none; margin:0; border: 0; padding: 0;" + onpopupshown="submenuOpened()"> + <menuitem label="Four"/> + </menupopup> + </menu> + </menupopup> + </menu> + <button label="2" maxwidth="100" maxheight="20" style="margin: 5px;"/> + <button label="3" maxwidth="100" maxheight="20" style="margin: 5px;"/> + <button label="4" maxwidth="100" maxheight="20" style="margin: 5px;"/> + </vbox> + <box height="200"/> + </scrollbox> + </deck> +</scrollbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTest() +{ + var winwidth = document.documentElement.boxObject.width; + var innerscroll = $("innerscroll").boxObject.width; + + var box1 = $("box1"); + checkPosition("box1", box1, 0, 0, winwidth, 46); + + var box2 = $("box2"); + checkPosition("box2", box2, 2, 46, winwidth, 96); + + // height is height(box1) = 46 + margin-top(box3) = 1 + margin-top(button1) = 5 + var button1 = $("button1"); + checkPosition("button1", button1, 9, 53, 99, 88); + + var sbo = box2.boxObject; + sbo.scrollTo(7, 16); + + // clientRect height is offset from root so is 16 pixels vertically less + checkPosition("button1 scrolled", button1, 9, 37, 99, 72); + + var box3 = $("box3"); + sbo = box3.boxObject; + sbo.scrollTo(1, 2); + + checkPosition("button1 scrolled", button1, 9, 35, 99, 70); + + $("menu").open = true; +} + +function menuOpened() +{ + $("submenu").open = true; +} + +function submenuOpened() +{ + var menu = $("menu"); + var menuleft = Math.round(menu.getBoundingClientRect().left); + var menubottom = Math.round(menu.getBoundingClientRect().bottom); + + var submenu = $("submenu"); + var submenutop = Math.round(submenu.getBoundingClientRect().top); + var submenuright = Math.round(submenu.getBoundingClientRect().right); + + checkPosition("popup", $("popup"), menuleft, menubottom, -1, -1); + checkPosition("subpopup", $("subpopup"), submenuright, submenutop, -1, -1); + + menu.open = false; +} + +function checkPosition(testid, elem, cleft, ctop, cright, cbottom) +{ + // -1 for right or bottom means that the exact size should not be + // checked, just ensure it is larger then the left or top position + var rect = elem.getBoundingClientRect(); + is(Math.round(rect.left), cleft, testid + " client rect left"); + if (testid != "popup") + is(Math.round(rect.top), ctop, testid + " client rect top"); + if (cright >= 0) + is(Math.round(rect.right), cright, testid + " client rect right"); + else + ok(rect.right - rect.left > 20, testid + " client rect right"); + if (cbottom >= 0) + is(Math.round(rect.bottom), cbottom, testid + " client rect bottom"); + else + ok(rect.bottom - rect.top > 15, testid + " client rect bottom"); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_preferences.xul b/toolkit/content/tests/chrome/test_preferences.xul new file mode 100644 index 0000000000..22e7e2fe96 --- /dev/null +++ b/toolkit/content/tests/chrome/test_preferences.xul @@ -0,0 +1,533 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Preferences Window Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="RunTest();"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + + const kPref = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + + // preference values, set 1 + const kPrefValueSet1 = + { + int: 23, + bool: true, + string: "rheeet!", + wstring_data: "日本語", + unichar_data: "äöüßÄÖÜ", + file_data: "/", + + wstring: Components.classes["@mozilla.org/pref-localizedstring;1"] + .createInstance(Components.interfaces.nsIPrefLocalizedString), + unichar: Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString), + file: Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile) + }; + kPrefValueSet1.wstring.data = kPrefValueSet1.wstring_data; + kPrefValueSet1.unichar.data = kPrefValueSet1.unichar_data; + SafeFileInit(kPrefValueSet1.file, kPrefValueSet1.file_data); + + // preference values, set 2 + const kPrefValueSet2 = + { + int: 42, + bool: false, + string: "Mozilla", + wstring_data: "헤드라인A", + unichar_data: "áôùšŽ", + file_data: "/home", + + wstring: Components.classes["@mozilla.org/pref-localizedstring;1"] + .createInstance(Components.interfaces.nsIPrefLocalizedString), + unichar: Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString), + file: Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile) + }; + kPrefValueSet2.wstring.data = kPrefValueSet2.wstring_data; + kPrefValueSet2.unichar.data = kPrefValueSet2.unichar_data; + SafeFileInit(kPrefValueSet2.file, kPrefValueSet2.file_data); + + + function SafeFileInit(aFile, aPath) + { + // set file path without dying for exceptions + try + { + aFile.initWithPath(aPath); + } + catch (ignored) {} + } + + function CreateEmptyPrefValueSet() + { + var result = + { + int: undefined, + bool: undefined, + string: undefined, + wstring_data: undefined, + unichar_data: undefined, + file_data: undefined, + wstring: undefined, + unichar: undefined, + file: undefined + }; + return result; + } + + function WritePrefsToSystem(aPrefValueSet) + { + // write preference data via XPCOM + kPref.setIntPref ("tests.static_preference_int", aPrefValueSet.int); + kPref.setBoolPref("tests.static_preference_bool", aPrefValueSet.bool); + kPref.setCharPref("tests.static_preference_string", aPrefValueSet.string); + kPref.setComplexValue("tests.static_preference_wstring", + Components.interfaces.nsIPrefLocalizedString, + aPrefValueSet.wstring); + kPref.setComplexValue("tests.static_preference_unichar", + Components.interfaces.nsISupportsString, + aPrefValueSet.unichar); + kPref.setComplexValue("tests.static_preference_file", + Components.interfaces.nsILocalFile, + aPrefValueSet.file); + } + + function ReadPrefsFromSystem() + { + // read preference data via XPCOM + var result = CreateEmptyPrefValueSet(); + try {result.int = kPref.getIntPref ("tests.static_preference_int") } catch (ignored) {}; + try {result.bool = kPref.getBoolPref("tests.static_preference_bool") } catch (ignored) {}; + try {result.string = kPref.getCharPref("tests.static_preference_string")} catch (ignored) {}; + try + { + result.wstring = kPref.getComplexValue("tests.static_preference_wstring", + Components.interfaces.nsIPrefLocalizedString); + result.wstring_data = result.wstring.data; + } + catch (ignored) {}; + try + { + result.unichar = kPref.getComplexValue("tests.static_preference_unichar", + Components.interfaces.nsISupportsString); + result.unichar_data = result.unichar.data; + } + catch (ignored) {}; + try + { + result.file = kPref.getComplexValue("tests.static_preference_file", + Components.interfaces.nsILocalFile); + result.file_data = result.file.data; + } + catch (ignored) {}; + return result; + } + + function GetXULElement(aPrefWindow, aID) + { + return aPrefWindow.document.getElementById(aID); + } + + function WritePrefsToPreferences(aPrefWindow, aPrefValueSet) + { + // write preference data into <preference>s + GetXULElement(aPrefWindow, "tests.static_preference_int" ).value = aPrefValueSet.int; + GetXULElement(aPrefWindow, "tests.static_preference_bool" ).value = aPrefValueSet.bool; + GetXULElement(aPrefWindow, "tests.static_preference_string" ).value = aPrefValueSet.string; + GetXULElement(aPrefWindow, "tests.static_preference_wstring").value = aPrefValueSet.wstring_data; + GetXULElement(aPrefWindow, "tests.static_preference_unichar").value = aPrefValueSet.unichar_data; + GetXULElement(aPrefWindow, "tests.static_preference_file" ).value = aPrefValueSet.file_data; + } + + function ReadPrefsFromPreferences(aPrefWindow) + { + // read preference data from <preference>s + var result = + { + int: GetXULElement(aPrefWindow, "tests.static_preference_int" ).value, + bool: GetXULElement(aPrefWindow, "tests.static_preference_bool" ).value, + string: GetXULElement(aPrefWindow, "tests.static_preference_string" ).value, + wstring_data: GetXULElement(aPrefWindow, "tests.static_preference_wstring").value, + unichar_data: GetXULElement(aPrefWindow, "tests.static_preference_unichar").value, + file_data: GetXULElement(aPrefWindow, "tests.static_preference_file" ).value, + wstring: Components.classes["@mozilla.org/pref-localizedstring;1"] + .createInstance(Components.interfaces.nsIPrefLocalizedString), + unichar: Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString), + file: Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile) + } + result.wstring.data = result.wstring_data; + result.unichar.data = result.unichar_data; + SafeFileInit(result.file, result.file_data); + return result; + } + + function WritePrefsToUI(aPrefWindow, aPrefValueSet) + { + // write preference data into UI elements + GetXULElement(aPrefWindow, "static_element_int" ).value = aPrefValueSet.int; + GetXULElement(aPrefWindow, "static_element_bool" ).checked = aPrefValueSet.bool; + GetXULElement(aPrefWindow, "static_element_string" ).value = aPrefValueSet.string; + GetXULElement(aPrefWindow, "static_element_wstring").value = aPrefValueSet.wstring_data; + GetXULElement(aPrefWindow, "static_element_unichar").value = aPrefValueSet.unichar_data; + GetXULElement(aPrefWindow, "static_element_file" ).value = aPrefValueSet.file_data; + } + + function ReadPrefsFromUI(aPrefWindow) + { + // read preference data from <preference>s + var result = + { + int: GetXULElement(aPrefWindow, "static_element_int" ).value, + bool: GetXULElement(aPrefWindow, "static_element_bool" ).checked, + string: GetXULElement(aPrefWindow, "static_element_string" ).value, + wstring_data: GetXULElement(aPrefWindow, "static_element_wstring").value, + unichar_data: GetXULElement(aPrefWindow, "static_element_unichar").value, + file_data: GetXULElement(aPrefWindow, "static_element_file" ).value, + wstring: Components.classes["@mozilla.org/pref-localizedstring;1"] + .createInstance(Components.interfaces.nsIPrefLocalizedString), + unichar: Components.classes["@mozilla.org/supports-string;1"] + .createInstance(Components.interfaces.nsISupportsString), + file: Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile) + } + result.wstring.data = result.wstring_data; + result.unichar.data = result.unichar_data; + SafeFileInit(result.file, result.file_data); + return result; + } + + + function RunInstantPrefTest(aPrefWindow) + { + // remark: there's currently no UI element binding for files + + // were all <preferences> correctly initialized? + var expected = kPrefValueSet1; + var found = ReadPrefsFromPreferences(aPrefWindow); + ok(found.int === expected.int, "instant pref init int" ); + ok(found.bool === expected.bool, "instant pref init bool" ); + ok(found.string === expected.string, "instant pref init string" ); + ok(found.wstring_data === expected.wstring_data, "instant pref init wstring"); + ok(found.unichar_data === expected.unichar_data, "instant pref init unichar"); + todo(found.file_data === expected.file_data, "instant pref init file" ); + + // were all elements correctly initialized? (loose check) + found = ReadPrefsFromUI(aPrefWindow); + ok(found.int == expected.int, "instant element init int" ); + ok(found.bool == expected.bool, "instant element init bool" ); + ok(found.string == expected.string, "instant element init string" ); + ok(found.wstring_data == expected.wstring_data, "instant element init wstring"); + ok(found.unichar_data == expected.unichar_data, "instant element init unichar"); + todo(found.file_data == expected.file_data, "instant element init file" ); + + // do some changes in the UI + expected = kPrefValueSet2; + WritePrefsToUI(aPrefWindow, expected); + + // UI changes should get passed to the <preference>s, + // but currently they aren't if the changes are made programmatically + // (the handlers preference.change/prefpane.input and prefpane.change + // are called for manual changes, though). + found = ReadPrefsFromPreferences(aPrefWindow); + todo(found.int === expected.int, "instant change pref int" ); + todo(found.bool === expected.bool, "instant change pref bool" ); + todo(found.string === expected.string, "instant change pref string" ); + todo(found.wstring_data === expected.wstring_data, "instant change pref wstring"); + todo(found.unichar_data === expected.unichar_data, "instant change pref unichar"); + todo(found.file_data === expected.file_data, "instant change pref file" ); + + // and these changes should get passed to the system instantly + // (which obviously can't pass with the above failing) + found = ReadPrefsFromSystem(); + todo(found.int === expected.int, "instant change element int" ); + todo(found.bool === expected.bool, "instant change element bool" ); + todo(found.string === expected.string, "instant change element string" ); + todo(found.wstring_data === expected.wstring_data, "instant change element wstring"); + todo(found.unichar_data === expected.unichar_data, "instant change element unichar"); + todo(found.file_data === expected.file_data, "instant change element file" ); + + // try resetting the prefs to default values (which should be empty here) + GetXULElement(aPrefWindow, "tests.static_preference_int" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_bool" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_string" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_wstring").reset(); + GetXULElement(aPrefWindow, "tests.static_preference_unichar").reset(); + GetXULElement(aPrefWindow, "tests.static_preference_file" ).reset(); + + // check system + expected = CreateEmptyPrefValueSet(); + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "instant reset system int" ); + ok(found.bool === expected.bool, "instant reset system bool" ); + ok(found.string === expected.string, "instant reset system string" ); + ok(found.wstring_data === expected.wstring_data, "instant reset system wstring"); + ok(found.unichar_data === expected.unichar_data, "instant reset system unichar"); + ok(found.file_data === expected.file_data, "instant reset system file" ); + + // check UI + expected = + { + // alas, we don't have XUL elements with typeof(value) == int :( + // int: 0, + int: "", + bool: false, + string: "", + wstring_data: "", + unichar_data: "", + file_data: "", + wstring: {}, + unichar: {}, + file: {} + }; + found = ReadPrefsFromUI(aPrefWindow); + ok(found.int === expected.int, "instant reset element int" ); + ok(found.bool === expected.bool, "instant reset element bool" ); + ok(found.string === expected.string, "instant reset element string" ); + ok(found.wstring_data === expected.wstring_data, "instant reset element wstring"); + ok(found.unichar_data === expected.unichar_data, "instant reset element unichar"); +// ok(found.file_data === expected.file_data, "instant reset element file" ); + + // check hasUserValue + ok(GetXULElement(aPrefWindow, "tests.static_preference_int" ).hasUserValue === false, "instant reset hasUserValue int" ); + ok(GetXULElement(aPrefWindow, "tests.static_preference_bool" ).hasUserValue === false, "instant reset hasUserValue bool" ); + ok(GetXULElement(aPrefWindow, "tests.static_preference_string" ).hasUserValue === false, "instant reset hasUserValue string" ); + ok(GetXULElement(aPrefWindow, "tests.static_preference_wstring").hasUserValue === false, "instant reset hasUserValue wstring"); + ok(GetXULElement(aPrefWindow, "tests.static_preference_unichar").hasUserValue === false, "instant reset hasUserValue unichar"); + ok(GetXULElement(aPrefWindow, "tests.static_preference_file" ).hasUserValue === false, "instant reset hasUserValue file" ); + + // done with instant apply checks + } + + function RunNonInstantPrefTestGeneral(aPrefWindow) + { + // Non-instant apply tests are harder: not only do we need to check that + // fiddling with the values does *not* change the system settings, but + // also that they *are* (not) set after closing (cancelling) the dialog... + + // remark: there's currently no UI element binding for files + + // were all <preferences> correctly initialized? + var expected = kPrefValueSet1; + var found = ReadPrefsFromPreferences(aPrefWindow); + ok(found.int === expected.int, "non-instant pref init int" ); + ok(found.bool === expected.bool, "non-instant pref init bool" ); + ok(found.string === expected.string, "non-instant pref init string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant pref init wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant pref init unichar"); + todo(found.file_data === expected.file_data, "non-instant pref init file" ); + + // were all elements correctly initialized? (loose check) + found = ReadPrefsFromUI(aPrefWindow); + ok(found.int == expected.int, "non-instant element init int" ); + ok(found.bool == expected.bool, "non-instant element init bool" ); + ok(found.string == expected.string, "non-instant element init string" ); + ok(found.wstring_data == expected.wstring_data, "non-instant element init wstring"); + ok(found.unichar_data == expected.unichar_data, "non-instant element init unichar"); + todo(found.file_data == expected.file_data, "non-instant element init file" ); + + // do some changes in the UI + expected = kPrefValueSet2; + WritePrefsToUI(aPrefWindow, expected); + + // UI changes should get passed to the <preference>s, + // but currently they aren't if the changes are made programmatically + // (the handlers preference.change/prefpane.input and prefpane.change + // are called for manual changes, though). + found = ReadPrefsFromPreferences(aPrefWindow); + todo(found.int === expected.int, "non-instant change pref int" ); + todo(found.bool === expected.bool, "non-instant change pref bool" ); + todo(found.string === expected.string, "non-instant change pref string" ); + todo(found.wstring_data === expected.wstring_data, "non-instant change pref wstring"); + todo(found.unichar_data === expected.unichar_data, "non-instant change pref unichar"); + todo(found.file_data === expected.file_data, "non-instant change pref file" ); + + // and these changes should *NOT* get passed to the system + // (which obviously always passes with the above failing) + expected = kPrefValueSet1; + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant change element int" ); + ok(found.bool === expected.bool, "non-instant change element bool" ); + ok(found.string === expected.string, "non-instant change element string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant change element wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant change element unichar"); + todo(found.file_data === expected.file_data, "non-instant change element file" ); + + // try resetting the prefs to default values (which should be empty here) + GetXULElement(aPrefWindow, "tests.static_preference_int" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_bool" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_string" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_wstring").reset(); + GetXULElement(aPrefWindow, "tests.static_preference_unichar").reset(); + GetXULElement(aPrefWindow, "tests.static_preference_file" ).reset(); + + // check system: the current values *MUST NOT* change + expected = kPrefValueSet1; + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant reset system int" ); + ok(found.bool === expected.bool, "non-instant reset system bool" ); + ok(found.string === expected.string, "non-instant reset system string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant reset system wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant reset system unichar"); + todo(found.file_data === expected.file_data, "non-instant reset system file" ); + + // check UI: these values should be reset + expected = + { + // alas, we don't have XUL elements with typeof(value) == int :( + // int: 0, + int: "", + bool: false, + string: "", + wstring_data: "", + unichar_data: "", + file_data: "", + wstring: {}, + unichar: {}, + file: {} + }; + found = ReadPrefsFromUI(aPrefWindow); + ok(found.int === expected.int, "non-instant reset element int" ); + ok(found.bool === expected.bool, "non-instant reset element bool" ); + ok(found.string === expected.string, "non-instant reset element string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant reset element wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant reset element unichar"); +// ok(found.file_data === expected.file_data, "non-instant reset element file" ); + + // check hasUserValue + ok(GetXULElement(aPrefWindow, "tests.static_preference_int" ).hasUserValue === false, "non-instant reset hasUserValue int" ); + ok(GetXULElement(aPrefWindow, "tests.static_preference_bool" ).hasUserValue === false, "non-instant reset hasUserValue bool" ); + ok(GetXULElement(aPrefWindow, "tests.static_preference_string" ).hasUserValue === false, "non-instant reset hasUserValue string" ); + ok(GetXULElement(aPrefWindow, "tests.static_preference_wstring").hasUserValue === false, "non-instant reset hasUserValue wstring"); + ok(GetXULElement(aPrefWindow, "tests.static_preference_unichar").hasUserValue === false, "non-instant reset hasUserValue unichar"); + ok(GetXULElement(aPrefWindow, "tests.static_preference_file" ).hasUserValue === false, "non-instant reset hasUserValue file" ); + } + + function RunNonInstantPrefTestClose(aPrefWindow) + { + WritePrefsToPreferences(aPrefWindow, kPrefValueSet2); + } + + function RunCheckCommandRedirect(aPrefWindow) + { + GetXULElement(aPrefWindow, "checkbox").click(); + ok(GetXULElement(aPrefWindow, "tests.static_preference_bool").value, "redirected command bool"); + GetXULElement(aPrefWindow, "checkbox").click(); + ok(!GetXULElement(aPrefWindow, "tests.static_preference_bool").value, "redirected command bool"); + } + + function RunResetPrefTest(aPrefWindow) + { + // try resetting the prefs to default values + GetXULElement(aPrefWindow, "tests.static_preference_int" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_bool" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_string" ).reset(); + GetXULElement(aPrefWindow, "tests.static_preference_wstring").reset(); + GetXULElement(aPrefWindow, "tests.static_preference_unichar").reset(); + GetXULElement(aPrefWindow, "tests.static_preference_file" ).reset(); + } + + function InitTestPrefs(aInstantApply) + { + // set instant apply mode and init prefs to set 1 + kPref.setBoolPref("browser.preferences.instantApply", aInstantApply); + WritePrefsToSystem(kPrefValueSet1); + } + + function RunTestInstant() + { + // test with instantApply + InitTestPrefs(true); + openDialog("window_preferences.xul", "", "modal", RunInstantPrefTest, false); + + // - test deferred reset in child window + InitTestPrefs(true); + openDialog("window_preferences2.xul", "", "modal", RunResetPrefTest, false); + expected = kPrefValueSet1; + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "instant reset deferred int" ); + ok(found.bool === expected.bool, "instant reset deferred bool" ); + ok(found.string === expected.string, "instant reset deferred string" ); + ok(found.wstring_data === expected.wstring_data, "instant reset deferred wstring"); + ok(found.unichar_data === expected.unichar_data, "instant reset deferred unichar"); + todo(found.file_data === expected.file_data, "instant reset deferred file" ); + } + + function RunTestNonInstant() + { + // test without instantApply + // - general tests, similar to instant apply + InitTestPrefs(false); + openDialog("window_preferences.xul", "", "modal", RunNonInstantPrefTestGeneral, false); + + // - test Cancel + InitTestPrefs(false); + openDialog("window_preferences.xul", "", "modal", RunNonInstantPrefTestClose, false); + var expected = kPrefValueSet1; + var found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant cancel system int" ); + ok(found.bool === expected.bool, "non-instant cancel system bool" ); + ok(found.string === expected.string, "non-instant cancel system string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant cancel system wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant cancel system unichar"); + todo(found.file_data === expected.file_data, "non-instant cancel system file" ); + + // - test Accept + InitTestPrefs(false); + openDialog("window_preferences.xul", "", "modal", RunNonInstantPrefTestClose, true); + expected = kPrefValueSet2; + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant accept system int" ); + ok(found.bool === expected.bool, "non-instant accept system bool" ); + ok(found.string === expected.string, "non-instant accept system string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant accept system wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant accept system unichar"); + todo(found.file_data === expected.file_data, "non-instant accept system file" ); + + // - test deferred reset in child window + InitTestPrefs(false); + openDialog("window_preferences2.xul", "", "modal", RunResetPrefTest, true); + expected = CreateEmptyPrefValueSet(); + found = ReadPrefsFromSystem(); + ok(found.int === expected.int, "non-instant reset deferred int" ); + ok(found.bool === expected.bool, "non-instant reset deferred bool" ); + ok(found.string === expected.string, "non-instant reset deferred string" ); + ok(found.wstring_data === expected.wstring_data, "non-instant reset deferred wstring"); + ok(found.unichar_data === expected.unichar_data, "non-instant reset deferred unichar"); + ok(found.file_data === expected.file_data, "non-instant reset deferred file" ); + } + + function RunTestCommandRedirect() + { + openDialog("window_preferences_commandretarget.xul", "", "modal", RunCheckCommandRedirect, true); + } + + function RunTest() + { + RunTestInstant(); + RunTestNonInstant(); + RunTestCommandRedirect(); + SimpleTest.finish(); + } + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_preferences_beforeaccept.xul b/toolkit/content/tests/chrome/test_preferences_beforeaccept.xul new file mode 100644 index 0000000000..a1abad3cc9 --- /dev/null +++ b/toolkit/content/tests/chrome/test_preferences_beforeaccept.xul @@ -0,0 +1,55 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Preferences Window beforeaccept Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({"set":[["browser.preferences.instantApply", false]]}, function() { + + // No instant-apply for this test + var prefWindow = openDialog("window_preferences_beforeaccept.xul", "", "", windowOnload); + + function windowOnload() { + var dialogShown = prefWindow.document.getElementById("tests.beforeaccept.dialogShown"); + var called = prefWindow.document.getElementById("tests.beforeaccept.called"); + is(dialogShown.value, true, "dialog opened, shown pref set"); + is(dialogShown.valueFromPreferences, null, "shown pref not committed"); + is(called.value, null, "beforeaccept not yet called"); + is(called.valueFromPreferences, null, "beforeaccept not yet called, pref not committed"); + + // try to accept the dialog, should fail the first time + prefWindow.document.documentElement.acceptDialog(); + is(prefWindow.closed, false, "window not closed"); + is(dialogShown.value, true, "shown pref still set"); + is(dialogShown.valueFromPreferences, null, "shown pref still not committed"); + is(called.value, true, "beforeaccept called"); + is(called.valueFromPreferences, null, "called pref not committed"); + + // try again, this one should succeed + prefWindow.document.documentElement.acceptDialog(); + is(prefWindow.closed, true, "window now closed"); + is(dialogShown.valueFromPreferences, true, "shown pref committed"); + is(called.valueFromPreferences, true, "called pref committed"); + + SimpleTest.finish(); + } +}); + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xul b/toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xul new file mode 100644 index 0000000000..8a191d97a1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xul @@ -0,0 +1,62 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- 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/. --> +<window title="Preferences Window beforeaccept Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <script type="application/javascript"> + <![CDATA[ + const PREFS = ['tests.onsyncfrompreference.pref1', + 'tests.onsyncfrompreference.pref2', + 'tests.onsyncfrompreference.pref3']; + + SimpleTest.waitForExplicitFinish(); + + for (let pref of PREFS) { + SpecialPowers.setIntPref(pref, 1); + } + + let counter = 0; + let prefWindow = openDialog("window_preferences_onsyncfrompreference.xul", "", "", onSync); + + SimpleTest.registerCleanupFunction(() => { + for (let pref of PREFS) { + SpecialPowers.clearUserPref(pref); + } + prefWindow.close(); + }); + + // Onsyncfrompreference handler for the prefs + function onSync() { + for (let pref of PREFS) { + // The `value` field of each <preference> element should be initialized by now. + + is(SpecialPowers.getIntPref(pref), prefWindow.document.getElementById(pref).value, + "Pref constructor was called correctly") + } + + counter++; + + if (counter == PREFS.length) { + SimpleTest.finish(); + } + return true; + } + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"></pre> + </body> + +</window> diff --git a/toolkit/content/tests/chrome/test_progressmeter.xul b/toolkit/content/tests/chrome/test_progressmeter.xul new file mode 100644 index 0000000000..7810f69915 --- /dev/null +++ b/toolkit/content/tests/chrome/test_progressmeter.xul @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for progressmeter + --> +<window title="Progressmeter" width="500" height="600" + onload="doTests()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <progressmeter id="n1"/> + <progressmeter id="n2" mode="undetermined"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +SimpleTest.waitForExplicitFinish(); + +function doTests() { + var n1 = document.getElementById("n1"); + var n2 = document.getElementById("n2"); + + SimpleTest.is(n1.mode, "", "mode determined"); + SimpleTest.is(n2.mode, "undetermined", "mode undetermined"); + + SimpleTest.is(n1.value, "0", "determined value"); + SimpleTest.is(n2.value, "0", "undetermined value"); + + // values can only be incremented in multiples of 4 + n1.value = 2; + SimpleTest.is(n1.value, "0", "determined value set 2"); + n1.value = -1; + SimpleTest.is(n1.value, "0", "determined value set -1"); + n1.value = 125; + SimpleTest.is(n1.value, "100", "determined value set 125"); + n1.value = 7; + SimpleTest.is(n1.value, "7", "determined value set 7"); + n1.value = "17"; + SimpleTest.is(n1.value, "17", "determined value set 17 string"); + n1.value = 18; + SimpleTest.is(n1.value, "17", "determined value set 18"); + n1.value = "Cat"; + SimpleTest.is(n1.value, "17", "determined value set invalid"); + + n1.max = 200; + is(n1.max, "200", "max changed"); + n1.value = 150; + n1.max = 120; + is(n1.value, "120", "max lowered below value"); + + n2.value = 2; + SimpleTest.is(n2.value, "0", "undetermined value set 2"); + n2.value = -1; + SimpleTest.is(n2.value, "0", "undetermined value set -1"); + n2.value = 125; + SimpleTest.is(n2.value, "100", "undetermined value set 125"); + n2.value = 7; + SimpleTest.is(n2.value, "7", "undetermined value set 7"); + n2.value = "17"; + SimpleTest.is(n2.value, "17", "undetermined value set 17 string"); + n2.value = 18; + SimpleTest.is(n2.value, "17", "undetermined value set 18"); + n2.value = "Cat"; + SimpleTest.is(n2.value, "17", "determined value set invalid"); + + SimpleTest.finish(); +} + +]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_props.xul b/toolkit/content/tests/chrome/test_props.xul new file mode 100644 index 0000000000..17513df5c7 --- /dev/null +++ b/toolkit/content/tests/chrome/test_props.xul @@ -0,0 +1,91 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for basic properties - this test checks that the basic + properties defined in general.xml and inherited by a number of elements + work properly. + --> +<window title="Basic Properties Test" + onload="setTimeout(test_props, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<command id="cmd_nothing"/> +<command id="cmd_action"/> + +<button id="button" label="Button" accesskey="B" + crop="end" image="happy.png" command="cmd_nothing"/> +<checkbox id="checkbox" label="Checkbox" accesskey="B" + crop="end" image="happy.png" command="cmd_nothing"/> +<radiogroup> + <radio id="radio" label="Radio Button" value="rb1" accesskey="B" + crop="end" image="happy.png" command="cmd_nothing"/> +</radiogroup> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_props() +{ + test_props_forelement($("button"), "Button", null); + test_props_forelement($("checkbox"), "Checkbox", null); + test_props_forelement($("radio"), "Radio Button", "rb1"); + + SimpleTest.finish(); +} + +function test_props_forelement(element, label, value) +{ + // check the initial values + is(element.label, label, "element label"); + if (value) + is(element.value, value, "element value"); + is(element.accessKey, "B", "element accessKey"); + is(element.crop, "end", "element crop"); + is(element.image, "happy.png", "element image"); + is(element.command, "cmd_nothing", "element command"); + ok(element.tabIndex === 0, "element tabIndex"); + + synthesizeMouseExpectEvent(element, 4, 4, { }, $("cmd_nothing"), "command", "element"); + + // make sure that setters return the value + is(element.label = "Label", "Label", "element label setter return"); + if (value) + is(element.value = "lb", "lb", "element value setter return"); + is(element.accessKey = "L", "L", "element accessKey setter return"); + is(element.crop = "start", "start", "element crop setter return"); + is(element.image = "sad.png", "sad.png", "element image setter return"); + is(element.command = "cmd_action", "cmd_action", "element command setter return"); + + // check the value after it was changed + is(element.label, "Label", "element label after set"); + if (value) + is(element.value, "lb", "element value after set"); + is(element.accessKey, "L", "element accessKey after set"); + is(element.crop, "start", "element crop after set"); + is(element.image, "sad.png", "element image after set"); + is(element.command, "cmd_action", "element command after set"); + + synthesizeMouseExpectEvent(element, 4, 4, { }, $("cmd_action"), "command", "element"); + + // check that clicks on disabled items don't fire a command event + ok((element.disabled = true) === true, "element disabled setter return"); + ok(element.disabled === true, "element disabled after set"); + synthesizeMouseExpectEvent(element, 4, 4, { }, $("cmd_action"), "!command", "element"); + + element.disabled = false; +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_radio.xul b/toolkit/content/tests/chrome/test_radio.xul new file mode 100644 index 0000000000..74ab66a34e --- /dev/null +++ b/toolkit/content/tests/chrome/test_radio.xul @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for radio buttons + --> +<window title="Radio Buttons" width="500" height="600" + onload="setTimeout(test_radio, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"/> + +<radiogroup id="radiogroup"/> + +<radiogroup id="radiogroup-initwithvalue" value="two"> + <radio label="One" value="one"/> + <radio label="Two" value="two"/> + <radio label="Three" value="three"/> +</radiogroup> +<radiogroup id="radiogroup-initwithselected" value="two"> + <radio id="one" label="One" value="one" accesskey="o"/> + <radio id="two" label="Two" value="two" accesskey="t"/> + <radio label="Three" value="three" selected="true"/> +</radiogroup> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_radio() +{ + var element = document.getElementById("radiogroup"); + test_nsIDOMXULSelectControlElement(element, "radio", null); + test_nsIDOMXULSelectControlElement_UI(element, null); + + window.blur(); + + var accessKeyDetails = (navigator.platform.indexOf("Mac") >= 0) ? + { altKey : true, ctrlKey : true } : + { altKey : true, shiftKey: true }; + synthesizeKey("t", accessKeyDetails); + + var radiogroup = $("radiogroup-initwithselected"); + is(document.activeElement, radiogroup, "accesskey focuses radiogroup"); + is(radiogroup.selectedItem, $("two"), "accesskey selects radio"); + + $("radiogroup-initwithvalue").focus(); + + $("one").disabled = true; + synthesizeKey("o", accessKeyDetails); + + is(document.activeElement, $("radiogroup-initwithvalue"), "accesskey on disabled radio doesn't focus"); + is(radiogroup.selectedItem, $("two"), "accesskey on disabled radio doesn't change selection"); + + SimpleTest.finish(); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_richlist_direction.xul b/toolkit/content/tests/chrome/test_richlist_direction.xul new file mode 100644 index 0000000000..f94f1b3ba6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_richlist_direction.xul @@ -0,0 +1,138 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for listbox direction + --> +<window title="Listbox direction test" + onload="test_richlistbox()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <richlistbox seltype="multiple" id="richlistbox" flex="1" minheight="80" maxheight="80" height="80" /> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var richListBox = document.getElementById("richlistbox"); + +function getScrollIndexAmount(aDirection) { + return (4 * aDirection + richListBox.currentIndex); +} + +function test_richlistbox() +{ + richListBox.minHeight = richListBox.maxHeight = richListBox.height = + 80 + (80 - richListBox.scrollBoxObject.height); + var height = richListBox.scrollBoxObject.height; + var item; + do { + item = richListBox.appendItem("Test", ""); + item.height = item.minHeight = item.maxHeight = Math.floor(height / 4); + } while (item.getBoundingClientRect().bottom < (height * 2)) + richListBox.appendItem("Test", ""); + richListBox.firstChild.nextSibling.id = "list-box-first"; + richListBox.lastChild.previousSibling.id = "list-box-last"; + + // direction = "reverse", the values here are backwards due to the fact that + // richlistboxes respond differently when a user initiates a selection + richListBox.dir = "reverse"; + var count = richListBox.itemCount; + richListBox.focus(); + richListBox.selectedIndex = count - 1; + sendKey("DOWN"); + is(richListBox.currentIndex, count - 2, "Selection should move to the next item"); + sendKey("UP"); + is(richListBox.currentIndex, count - 1, "Selection should move to the previous item"); + sendKey("END"); + is(richListBox.currentIndex, 0, "Selection should move to the last item"); + sendKey("HOME"); + is(richListBox.currentIndex, count - 1, "Selection should move to the first item"); + var currentIndex = richListBox.currentIndex; + var index = getScrollIndexAmount(-1); + sendKey("PAGE_DOWN"); + is(richListBox.currentIndex, index, "Selection should move to one page down"); + ok(richListBox.currentIndex < currentIndex, "Selection should move downwards"); + sendKey("END"); + currentIndex = richListBox.currentIndex; + index = getScrollIndexAmount(1); + sendKey("PAGE_UP"); + is(richListBox.currentIndex, index, "Selection should move to one page up"); + ok(richListBox.currentIndex > currentIndex, "Selection should move upwards"); + richListBox.selectedItem = richListBox.lastChild; + richListBox.focus(); + synthesizeKey("KEY_ArrowDown", { shiftKey: true, code: "ArrowDown" }, window); + let items = [richListBox.selectedItems[0], + richListBox.selectedItems[1]]; + is(items[0], richListBox.lastChild, "The last element should still be selected"); + is(items[1], richListBox.lastChild.previousSibling, "Both elements should now be selected"); + richListBox.clearSelection(); + richListBox.selectedItem = richListBox.lastChild; + sendMouseEvent({type: "click", shiftKey: true, clickCount: 1}, + "list-box-last", + window); + items = [richListBox.selectedItems[0], + richListBox.selectedItems[1]]; + is(items[0], richListBox.lastChild, "The last element should still be selected"); + is(items[1], richListBox.lastChild.previousSibling, "Both elements should now be selected"); + richListBox.addEventListener("keypress", function(aEvent) { + richListBox.removeEventListener("keypress", arguments.callee, true); + aEvent.preventDefault(); + }, true); + richListBox.selectedIndex = 1; + sendKey("HOME"); + is(richListBox.selectedIndex, 1, "A stopped event should return indexing to normal"); + + // direction = "normal" + richListBox.dir = "normal"; + richListBox.selectedIndex = 0; + sendKey("DOWN"); + is(richListBox.currentIndex, 1, "Selection should move to the next item"); + sendKey("UP"); + is(richListBox.currentIndex, 0, "Selection should move to the previous item"); + sendKey("END"); + is(richListBox.currentIndex, count - 1, "Selection should move to the last item"); + sendKey("HOME"); + is(richListBox.currentIndex, 0, "Selection should move to the first item"); + var currentIndex = richListBox.currentIndex; + var index = richListBox.scrollOnePage(1); + sendKey("PAGE_DOWN"); + is(richListBox.currentIndex, index, "Selection should move to one page down"); + ok(richListBox.currentIndex > currentIndex, "Selection should move downwards"); + sendKey("END"); + currentIndex = richListBox.currentIndex; + index = richListBox.scrollOnePage(-1) + richListBox.currentIndex; + sendKey("PAGE_UP"); + is(richListBox.currentIndex, index, "Selection should move to one page up"); + ok(richListBox.currentIndex < currentIndex, "Selection should move upwards"); + richListBox.selectedItem = richListBox.firstChild; + richListBox.focus(); + synthesizeKey("KEY_ArrowDown", { shiftKey: true, code: "ArrowDown" }, window); + items = [richListBox.selectedItems[0], + richListBox.selectedItems[1]]; + is(items[0], richListBox.firstChild, "The last element should still be selected"); + is(items[1], richListBox.firstChild.nextSibling, "Both elements should now be selected"); + richListBox.clearSelection(); + richListBox.selectedItem = richListBox.firstChild; + sendMouseEvent({type: "click", shiftKey: true, clickCount: 1}, + "list-box-first", + window); + items = [richListBox.selectedItems[0], + richListBox.selectedItems[1]]; + is(items[0], richListBox.firstChild, "The last element should still be selected"); + is(items[1], richListBox.firstChild.nextSibling, "Both elements should now be selected"); + SimpleTest.finish(); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_righttoleft.xul b/toolkit/content/tests/chrome/test_righttoleft.xul new file mode 100644 index 0000000000..64b1419daa --- /dev/null +++ b/toolkit/content/tests/chrome/test_righttoleft.xul @@ -0,0 +1,126 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> + +<window title="Right to Left UI Test" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/chrome-harness.js"></script> + <script type="application/javascript" + src="RegisterUnregisterChrome.js"></script> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + </pre> + </body> + + <iframe id="subframe" width="100" height="100" onload="frameLoaded();"/> + + <script type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + let prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + const UI_DIRECTION_PREF = "intl.uidirection.ar"; + prefs.setCharPref(UI_DIRECTION_PREF, "rtl"); + + let rootDir = getRootDirectory(window.location.href); + registerManifestPermanently(rootDir + "rtltest/righttoleft.manifest"); + + function runTest() + { + var subframe = document.getElementById("subframe"); + subframe.setAttribute("src", "chrome://ltrtest/content/dirtest.xul"); + } + + function frameLoaded() + { + var subframe = document.getElementById("subframe"); + var subwin = subframe.contentWindow; + var subdoc = subframe.contentDocument; + var url = String(subwin.location); + if (url.indexOf("chrome://ltrtest") == 0) { + is(subwin.getComputedStyle(subdoc.getElementById("hbox"), "").backgroundColor, + "rgb(255, 255, 0)", "left to right with :-moz-locale-dir(ltr)"); + is(subwin.getComputedStyle(subdoc.getElementById("vbox"), "").backgroundColor, + "rgb(255, 255, 255)", "left to right with :-moz-locale-dir(rtl)"); + + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "ltr", + "left to right direction"); + + subdoc.documentElement.setAttribute("localedir", "rtl"); + + is(subwin.getComputedStyle(subdoc.getElementById("hbox"), "").backgroundColor, + "rgb(255, 255, 255)", "left to right with :-moz-locale-dir(ltr) and localedir='rtl'"); + is(subwin.getComputedStyle(subdoc.getElementById("vbox"), "").backgroundColor, + "rgb(0, 128, 0)", "left to right with :-moz-locale-dir(rtl) and localedir='rtl'"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "rtl", + "left to right direction with localedir='rtl'"); + + subdoc.documentElement.removeAttribute("localedir"); + + is(subwin.getComputedStyle(subdoc.getElementById("hbox"), "").backgroundColor, + "rgb(255, 255, 0)", "left to right with :-moz-locale-dir(ltr) and localedir removed"); + is(subwin.getComputedStyle(subdoc.getElementById("vbox"), "").backgroundColor, + "rgb(255, 255, 255)", "left to right with :-moz-locale-dir(rtl) and localedir removed"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "ltr", + "left to right direction with localedir removed"); + + subframe.setAttribute("src", "chrome://rtltest/content/dirtest.xul"); + } + else if (url.indexOf("chrome://rtltest") == 0) { + is(subwin.getComputedStyle(subdoc.getElementById("hbox"), "").backgroundColor, + "rgb(255, 255, 255)", "right to left with :-moz-locale-dir(ltr)"); + is(subwin.getComputedStyle(subdoc.getElementById("vbox"), "").backgroundColor, + "rgb(0, 128, 0)", "right to left with :-moz-locale-dir(rtl)"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "rtl", + "right to left direction"); + + subdoc.documentElement.setAttribute("localedir", "ltr"); + + is(subwin.getComputedStyle(subdoc.getElementById("hbox"), "").backgroundColor, + "rgb(255, 255, 0)", "right to left with :-moz-locale-dir(ltr) and localedir='ltr'"); + is(subwin.getComputedStyle(subdoc.getElementById("vbox"), "").backgroundColor, + "rgb(255, 255, 255)", "right to left with :-moz-locale-dir(rtl) and localedir='ltr'"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "ltr", + "right to left direction with localedir='ltr'"); + + subdoc.documentElement.removeAttribute("localedir"); + + prefs.setCharPref(UI_DIRECTION_PREF, ""); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "ltr", + "left to right direction with no preference set"); + prefs.setCharPref(UI_DIRECTION_PREF + "-QA", "rtl"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "rtl", + "right to left direction with more specific preference set"); + prefs.setCharPref(UI_DIRECTION_PREF, "ltr"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "rtl", + "right to left direction with less specific and more specific preference set"); + prefs.setCharPref(UI_DIRECTION_PREF, "rtl"); + prefs.setCharPref(UI_DIRECTION_PREF + "-QA", "ltr"); + is(subwin.getComputedStyle(subdoc.documentElement, "").direction, "ltr", + "left to right direction specific preference overrides"); + if (prefs.prefHasUserValue(UI_DIRECTION_PREF + "-QA")) + prefs.clearUserPref(UI_DIRECTION_PREF + "-QA"); + + if (prefs.prefHasUserValue(UI_DIRECTION_PREF)) + prefs.clearUserPref(UI_DIRECTION_PREF); + + SimpleTest.finish(); + } + } + ]]> + </script> + +</window> diff --git a/toolkit/content/tests/chrome/test_scale.xul b/toolkit/content/tests/chrome/test_scale.xul new file mode 100644 index 0000000000..c1adea7ffc --- /dev/null +++ b/toolkit/content/tests/chrome/test_scale.xul @@ -0,0 +1,277 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for scale + --> +<window title="scale" width="500" height="600" + onload="setTimeout(testtag_scale, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<hbox> + <vbox> + <scale id="scale-horizontal-normal"/> + <scale id="scale-horizontal-reverse" dir="reverse"/> + </vbox> + <scale id="scale-vertical-normal" orient="vertical"/> + <scale id="scale-vertical-reverse" orient="vertical" dir="reverse"/> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function testtag_scale() +{ + testtag_scale_inner("scale-horizontal-normal", true, false); + testtag_scale_inner("scale-horizontal-reverse", true, true); + testtag_scale_inner("scale-vertical-normal", false, false); + testtag_scale_inner("scale-vertical-reverse", false, true); + + SimpleTest.finish(); +} + +function testtag_scale_inner(elementid, horiz, reverse) +{ + var testid = (horiz ? "horizontal " : "vertical ") + + (reverse ? "reverse " : "normal "); + + var element = document.getElementById(elementid); + + testtag_scale_States(element, 0, "", 0, 100, testid + "initial"); + + element.min = 0; + element.max = 10; + testtag_scale_States(element, 0, "", 0, 10, testid + "first set"); + + element.decrease(); + is(element.value, 0, testid + "decrease"); + element.increase(); + is(element.value, 1, testid + "increase"); + element.value = 0; + is(element.value, 0, testid + "set value"); + + testtag_scale_Increments(element, 0, 10, 6, 7, testid + "increase decrease"); + + // check if changing the min and max adjusts the value to fit in range + element.min = 5; + testtag_scale_States(element, 5, "5", 5, 10, testid + "change min"); + element.value = 15; + is(element.value, 10, testid + "change minmax value set too high"); + element.max = 8; + is(element.value, 8, testid + "change max"); + element.value = 2; + is(element.value, 5, testid + "change minmax set too low"); + + // check negative values + element.min = -15; + element.max = -5; + testtag_scale_States(element, -5, "-5", -15, -5, testid + "minmax negative"); + element.value = -15; + is(element.value, -15, testid + "change max negative"); + testtag_scale_Increments(element, -15, -5, 7, 8, testid + "increase decrease negative"); + + // check case when min is negative and max is positive + element.min = -10; + element.max = 35; + testtag_scale_States(element, -10, "-10", -10, 35, testid + "minmax mixed sign"); + testtag_scale_Increments(element, -10, 35, 25, 30, testid + "increase decrease mixed sign"); + + testtag_scale_UI(element, testid, horiz, reverse); +} + +function testtag_scale_UI(element, testid, horiz, reverse) +{ + element.min = 0; + element.max = 20; + element.value = 7; + element.increment = 2; + element.pageIncrement = 4; + + element.focus(); + + var leftIncrements = horiz && reverse; + var upDecrements = !horiz && !reverse; + synthesizeKeyExpectEvent("VK_LEFT", { }, element, "change", testid + "key left"); + is(element.value, leftIncrements ? 9 : 5, testid + " key left"); + synthesizeKeyExpectEvent("VK_RIGHT", { }, element, "change", testid + "key right"); + is(element.value, 7, testid + " key right"); + synthesizeKeyExpectEvent("VK_UP", { }, element, "change", testid + "key up"); + is(element.value, upDecrements ? 5 : 9, testid + " key up"); + synthesizeKeyExpectEvent("VK_DOWN", { }, element, "change", testid + "key down"); + is(element.value, 7, testid + " key down"); + + synthesizeKeyExpectEvent("VK_PAGE_UP", { }, element, "change", testid + "key page up"); + is(element.value, upDecrements ? 3 : 11, testid + " key page up"); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { }, element, "change", testid + "key page down"); + is(element.value, 7, testid + " key page down"); + + synthesizeKeyExpectEvent("VK_HOME", { }, element, "change", testid + "key home"); + is(element.value, reverse ? 20 : 0, testid + " key home"); + synthesizeKeyExpectEvent("VK_END", { }, element, "change", testid + "key end"); + is(element.value, reverse ? 0 : 20, testid + " key end"); + + testtag_scale_UI_Mouse(element, testid, horiz, reverse, 4); + + element.min = 4; + element.pageIncrement = 3; + testtag_scale_UI_Mouse(element, testid + " with min", horiz, reverse, 3); + + element.pageIncrement = 30; + testtag_scale_UI_Mouse(element, testid + " with min past", horiz, reverse, 30); +} + +function testtag_scale_UI_Mouse(element, testid, horiz, reverse, pinc) +{ + var initial = reverse ? 8 : 12; + var newval = initial + (reverse ? pinc : -pinc); + var endval = initial; + // in the pinc == 30 case, the page increment is large enough that it would + // just cause the thumb to reach the end of the scale. Make sure that the + // mouse click does not go past the end. + if (pinc == 30) { + newval = reverse ? 20 : 4; + endval = reverse ? 4 : 20; + } + element.value = initial; + + var hmove = horiz ? 25 : 10; + var vmove = horiz ? 10 : 25; + + var leftFn = function () { return reverse ? element.value < newval + 3 : element.value > newval - 3; } + var rightFn = function () { return reverse ? element.value < endval - 3 : element.value < endval + 3; } + + // check that clicking the mouse on the trough moves the thumb properly + synthesizeMouseExpectEvent(element, hmove, vmove, { }, element, "change", testid + "mouse on left movetoclick=default"); + + if (navigator.platform.indexOf("Mac") >= 0) { + if (pinc == 30) + ok(element.value > 4, testid + " mouse on left movetoclick=default"); + else + ok(leftFn, testid + " mouse on left movetoclick=default"); + } + else { + is(element.value, newval, testid + " mouse on left movetoclick=default"); + } + + var rect = element.getBoundingClientRect(); + synthesizeMouseExpectEvent(element, rect.right - rect.left - hmove, + rect.bottom - rect.top - vmove, { }, + element, "change", testid + " mouse on right movetoclick=default"); + + if (navigator.platform.indexOf("Mac") >= 0) { + if (pinc == 30) + ok(element.value < 20, testid + " mouse on right movetoclick=default"); + else + ok(rightFn, testid + " mouse on right movetoclick=default"); + } + else { + is(element.value, endval, testid + " mouse on right movetoclick=default"); + } + + element.setAttribute("movetoclick", "true"); + element.value = initial; + + synthesizeMouseExpectEvent(element, hmove, vmove, { }, element, "change", testid + "mouse on left movetoclick=true"); + if (pinc == 30) + ok(element.value > 4, testid + " mouse on left movetoclick=true"); + else + ok(leftFn, testid + " mouse on left movetoclick=true"); + + var rect = element.getBoundingClientRect(); + synthesizeMouseExpectEvent(element, rect.right - rect.left - hmove, + rect.bottom - rect.top - vmove, { }, + element, "change", testid + " mouse on right movetoclick=true"); + if (pinc == 30) + ok(element.value < 20, testid + " mouse on right movetoclick=true"); + else + ok(rightFn, testid + " mouse on right movetoclick=true"); + + element.setAttribute("movetoclick", "false"); + element.value = initial; + + synthesizeMouseExpectEvent(element, hmove, vmove, { }, element, "change", testid + "mouse on left movetoclick=false"); + is(element.value, newval, testid + " mouse on left movetoclick=false"); + + var rect = element.getBoundingClientRect(); + synthesizeMouseExpectEvent(element, rect.right - rect.left - hmove, + rect.bottom - rect.top - vmove, { }, + element, "change", testid + " mouse on right movetoclick=false"); + is(element.value, endval, testid + " mouse on right movetoclick=false"); + + element.removeAttribute("movetoclick"); + + element.value = reverse ? element.max : element.min; + + synthesizeMouse(element, 8, 8, { type: "mousedown" }); + synthesizeMouse(element, horiz ? 2000 : 8, horiz ? 8 : 2000, { type: "mousemove" }); + is(element.value, reverse ? element.min : element.max, testid + " move mouse too far after end"); + synthesizeMouse(element, 2, 2, { type: "mouseup" }); + + synthesizeMouse(element, rect.width - 8, rect.height - 8, { type: "mousedown" }); + synthesizeMouse(element, horiz ? -2000 : rect.width - 8, horiz ? rect.height - 8 : -2000, { type: "mousemove" }); + is(element.value, reverse ? element.max : element.min, testid + " move mouse too far before start"); + + synthesizeMouse(element, 2, 2, { type: "mouseup" }); + + // now check if moving outside in both directions works. On Windows, + // it should snap back to the original location. + element.value = reverse ? element.max : element.min; + + var expected = (navigator.platform.indexOf("Win") >= 0) ? element.value : + (reverse ? element.min : element.max); + synthesizeMouse(element, 7, 7, { type: "mousedown" }); + synthesizeMouse(element, 2000, 2000, { type: "mousemove" }); + is(element.value, expected, testid + " move mouse ouside in both directions"); + synthesizeMouse(element, 2, 2, { type: "mouseup" }); +} + +function testtag_scale_States(element, evalue, evalueattr, emin, emax, testid) +{ + is(element.getAttribute("value"), evalueattr, testid + " value attribute"); + is(element.value, evalue, testid + " value"); + is(element.min, emin, testid + " min"); + is(element.max, emax, testid + " max"); +} + +function testtag_scale_Increments(element, min, max, increment, pageIncrement, testid) +{ + // check the increase and decrease methods + element.increment = increment; + element.increase(); + is(element.value, min + increment, testid + " increase 1"); + element.increase(); + is(element.value, max, testid + " increase 2"); + element.decrease(); + is(element.value, max - increment, testid + " decrease 1"); + element.decrease(); + is(element.value, min, testid + " decrease 2"); + + // check the increasePage and decreasePage methods + element.pageIncrement = pageIncrement; + element.increasePage(); + is(element.value, min + pageIncrement, testid + " increasePage 1"); + element.increasePage(); + is(element.value, max, testid + " increasePage 2"); + element.decreasePage(); + is(element.value, max - pageIncrement, testid + " decreasePage 1"); + element.decreasePage(); + is(element.value, min, testid + " decreasePage 2"); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_scaledrag.xul b/toolkit/content/tests/chrome/test_scaledrag.xul new file mode 100644 index 0000000000..82356ca1ec --- /dev/null +++ b/toolkit/content/tests/chrome/test_scaledrag.xul @@ -0,0 +1,197 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +XUL <scale> dragging tests +--> +<window title="Dragging XUL scale tests" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox flex="1"> + <scale id="scale1" orient="horizontal" flex="1" min="0" max="4" value="2"/> + <scale id="scale2" orient="vertical" flex="1" min="0" max="4" value="2"/> + <scale id="scale3" orient="horizontal" flex="1" movetoclick="true" min="0" max="4" value="2"/> + </hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); +function getThumb(aScale) { + return document.getAnonymousElementByAttribute(aScale, "class", "scale-thumb"); +} + +function sendTouch(aType, aRect, aDX, aDY, aMods) { + var cwu = SpecialPowers.getDOMWindowUtils(window); + var x = aRect.left + aRect.width/2 + aDX; + var y = aRect.top + aRect.height/2 + aDY; + if (/mouse/.test(aType)) + cwu.sendMouseEvent(aType, x, y, 0, 1, aMods || 0, false); + else + cwu.sendTouchEvent(aType, [0], [x], [y], [1], [1], [0], [1], 1, aMods || 0, true); +} + +function getOffset(aScale, aDir) { + var rect = aScale.getBoundingClientRect(); + var d = aScale.orient == "horizontal" ? rect.width/4 : rect.height/4; + switch (aDir) { + case "right": return [ d, 0]; + case "left": return [-1*d, 0]; + case "up": return [ 0,-1*d]; + case "down": return [ 0, d]; + case "downleft": return [ -1*d, d]; + case "upleft": return [ -1*d,-1*d]; + case "downright": return [d, d]; + case "upright": return [d,-1*d]; + } + return [0,0]; +} + +function testTouchDragThumb(aDesc, aId, aDir, aVal1, aVal2, aMods) { + info(aDesc); + var scale = document.getElementById(aId); + var [x,y] = getOffset(scale, aDir); + + sendTouch("touchstart", getThumb(scale).getBoundingClientRect(), 0, 0, aMods); + is(scale.value, aVal1, "Touchstart on thumb has correct value"); + sendTouch("touchmove", getThumb(scale).getBoundingClientRect(), x, y, aMods); + sendTouch("touchend", getThumb(scale).getBoundingClientRect(), 0, 0, aMods); + is(scale.value, aVal2, "After touch " + (aDir ? ("and drag " + aDir + " ") : "") + "on thumb, scale has correct value"); + + scale.value = 2; +} + +function testMouseDragThumb(aDesc, aId, aDir, aVal1, aVal2, aMods) { + info(aDesc); + var scale = document.getElementById(aId); + var [x,y] = getOffset(scale, aDir); + + sendTouch("mousedown", getThumb(scale).getBoundingClientRect(), 0, 0, aMods); + is(scale.value, aVal1, "Mousedown on thumb has correct value"); + sendTouch("mousemove", getThumb(scale).getBoundingClientRect(), x, y, aMods); + sendTouch("mouseup", getThumb(scale).getBoundingClientRect(), 0, 0, aMods); + is(scale.value, aVal2, "After mouseup " + (aDir ? ("and drag " + aDir + " ") : "") + "on thumb, scale has correct value"); + + scale.value = 2; +} + +function testTouchDragSlider(aDesc, aId, aDir, aVal1, aVal2, aMods) { + info(aDesc); + var scale = document.getElementById(aId); + var [x,y] = getOffset(scale, aDir); + + sendTouch("touchstart", getThumb(scale).getBoundingClientRect(), x, y, aMods); + is(scale.value, aVal1, "Touchstart on slider has correct value"); + sendTouch("touchmove", getThumb(scale).getBoundingClientRect(), -x, -y, aMods); + sendTouch("touchend", getThumb(scale).getBoundingClientRect(), 0, 0, aMods); + is(scale.value, aVal2, "After touch " + (aDir ? ("and drag " + aDir + " ") : "") + "on slider, scale has correct value"); + + scale.value = 2; +} + +function testMouseDragSlider(aDesc, aId, aDir, aVal1, aVal2, aMods) { + info(aDesc); + var scale = document.getElementById(aId); + var [x,y] = getOffset(scale, aDir); + + sendTouch("mousedown", getThumb(scale).getBoundingClientRect(), x, y, aMods); + is(scale.value, aVal1, "Mousedown on slider has correct value"); + sendTouch("mousemove", getThumb(scale).getBoundingClientRect(), -x, -y, aMods); + sendTouch("mouseup", getThumb(scale).getBoundingClientRect(), 0, 0, aMods); + is(scale.value, aVal2, "After mouseup " + (aDir ? ("and drag " + aDir + " ") : "") + "on slider, scale has correct value"); + + scale.value = 2; +} + +function runTests() { + // test dragging a horizontal slider with touch events by tapping on the thumb + testTouchDragThumb("Touch Horizontal Thumb", "scale1", "", 2, 2); + testTouchDragThumb("TouchDrag Horizontal Thumb Left", "scale1", "left", 2, 1); + testTouchDragThumb("TouchDrag Horizontal Thumb Right", "scale1", "right", 2, 3); + testTouchDragThumb("TouchDrag Horizontal Thumb Up", "scale1", "up", 2, 2); + testTouchDragThumb("TouchDrag Horizontal Thumb Down", "scale1", "down", 2, 2); + testTouchDragThumb("TouchDrag Horizontal Thumb Downleft", "scale1", "downleft", 2, 1); + testTouchDragThumb("TouchDrag Horizontal Thumb Upleft", "scale1", "upleft", 2, 1); + testTouchDragThumb("TouchDrag Horizontal Thumb Upright", "scale1", "upright", 2, 3); + testTouchDragThumb("TouchDrag Horizontal Thumb Downright", "scale1", "downright", 2, 3); + + // test dragging a horizontal slider with mouse events by clicking on the thumb + testMouseDragThumb("Click Horizontal Thumb", "scale1", "", 2, 2); + testMouseDragThumb("MouseDrag Horizontal Thumb Left", "scale1", "left", 2, 1); + testMouseDragThumb("MouseDrag Horizontal Thumb Right", "scale1", "right", 2, 3); + testMouseDragThumb("MouseDrag Horizontal Thumb Up", "scale1", "up", 2, 2); + testMouseDragThumb("MouseDrag Horizontal Thumb Down", "scale1", "down", 2, 2); + testMouseDragThumb("MouseDrag Horizontal Thumb Downleft", "scale1", "downleft", 2, 1); + testMouseDragThumb("MouseDrag Horizontal Thumb Upleft", "scale1", "upleft", 2, 1); + testMouseDragThumb("MouseDrag Horizontal Thumb Upright", "scale1", "upright", 2, 3); + testMouseDragThumb("MouseDrag Horizontal Thumb Downright", "scale1", "downright", 2, 3); + + // test dragging a vertical slider with touch events by tapping on the thumb + testTouchDragThumb("Touch Vertical Thumb", "scale2", "", 2, 2); + testTouchDragThumb("TouchDrag Vertical Thumb Left", "scale2", "left", 2, 2); + testTouchDragThumb("TouchDrag Vertical Thumb Right", "scale2", "right", 2, 2); + testTouchDragThumb("TouchDrag Vertical Thumb Up", "scale2", "up", 2, 1); + testTouchDragThumb("TouchDrag Vertical Thumb Down", "scale2", "down", 2, 3); + testTouchDragThumb("TouchDrag Vertical Thumb Downleft", "scale2", "downleft", 2, 3); + testTouchDragThumb("TouchDrag Vertical Thumb Upleft", "scale2", "upleft", 2, 1); + testTouchDragThumb("TouchDrag Vertical Thumb Upright", "scale2", "upright", 2, 1); + testTouchDragThumb("TouchDrag Vertical Thumb Downright", "scale2", "downright", 2, 3); + + // test dragging a vertical slider with mouse events by clicking on the thumb + testMouseDragThumb("Click Vertical Thumb", "scale2", "", 2, 2); + testMouseDragThumb("MouseDrag Vertical Thumb Left", "scale2", "left", 2, 2); + testMouseDragThumb("MouseDrag Vertical Thumb Right", "scale2", "right", 2, 2); + testMouseDragThumb("MouseDrag Vertical Thumb Up", "scale2", "up", 2, 1); + testMouseDragThumb("MouseDrag Vertical Thumb Down", "scale2", "down", 2, 3); + testMouseDragThumb("MouseDrag Vertical Thumb Downleft", "scale2", "downleft", 2, 3); + testMouseDragThumb("MouseDrag Vertical Thumb Upleft", "scale2", "upleft", 2, 1); + testMouseDragThumb("MouseDrag Vertical Thumb Upright", "scale2", "upright", 2, 1); + testMouseDragThumb("MouseDrag Vertical Thumb Downright", "scale2", "downright", 2, 3); + + var isMac = /Mac/.test(navigator.platform); + + // test dragging a slider by tapping off the thumb + testTouchDragSlider("TouchDrag Slider Left", "scale1", "left", isMac ? 1 : 0, isMac ? 2 : 0); + testTouchDragSlider("TouchDrag Slider Right", "scale1", "right", isMac ? 3 : 4, isMac ? 2 : 4); + testMouseDragSlider("MouseDrag Slider Left", "scale1", "left", isMac ? 1 : 0, isMac ? 2 : 0); + testMouseDragSlider("MouseDrag Slider Right", "scale1", "right", isMac ? 3 : 4, isMac ? 2 : 4); + + // test dragging a slider by tapping off the thumb and holding shift + // modifiers don't affect touch events + var mods = /Mac/.test(navigator.platform) ? Components.interfaces.nsIDOMNSEvent.ALT_MASK : + Components.interfaces.nsIDOMNSEvent.SHIFT_MASK; + testTouchDragSlider("TouchDrag Slider Left+Shift", "scale1", "left", isMac ? 1 : 0, isMac ? 2 : 0, mods); + testTouchDragSlider("TouchDrag Slider Right+Shift", "scale1", "right", isMac ? 3 : 4, isMac ? 2 : 4, mods); + testMouseDragSlider("MouseDrag Slider Left+Shift", "scale1", "left", isMac ? 0 : 1, isMac ? 0 : 2, mods); + testMouseDragSlider("MouseDrag Slider Right+Shift", "scale1", "right", isMac ? 4 : 3, isMac ? 4 : 2, mods); + + // test dragging a slider with movetoclick="true" by tapping off the thumb + testTouchDragSlider("TouchDrag Slider Left+MoveToClick", "scale3", "left", 1, 2); + testTouchDragSlider("TouchDrag Slider Right+MoveToClick", "scale3", "right", 3, 2); + testMouseDragSlider("MouseDrag Slider Left+MoveToClick", "scale3", "left", 1, 2); + testMouseDragSlider("MouseDrag Slider Right+MoveToClick", "scale3", "right", 3, 2); + + // test dragging a slider by tapping off the thumb and holding shift + // modifiers don't affect touch events + testTouchDragSlider("MouseDrag Slider Left+MoveToClick+Shift", "scale3", "left", 1, 2, mods); + testTouchDragSlider("MouseDrag Slider Right+MoveToClick+Shift", "scale3", "right", 3, 2, mods); + testMouseDragSlider("MouseDrag Slider Left+MoveToClick+Shift", "scale3", "left", 0, 0, mods); + testMouseDragSlider("MouseDrag Slider Right+MoveToClick+Shift", "scale3", "right", 4, 4, mods); + + SimpleTest.finish(); +} + +addLoadEvent(function() { SimpleTest.executeSoon(runTests); }); +]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_screenPersistence.xul b/toolkit/content/tests/chrome/test_screenPersistence.xul new file mode 100644 index 0000000000..7d63252939 --- /dev/null +++ b/toolkit/content/tests/chrome/test_screenPersistence.xul @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Window Open Test" + onload="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script class="testbody" type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + let win; + var left = 60 + screen.availLeft; + var upper = 60 + screen.availTop; + + function runTest() { + win = window.openDialog("window_screenPosSize.xul", + null, + "chrome,dialog=no,all,screenX=" + left + ",screenY=" + upper + ",outerHeight=200,outerWidth=200"); + SimpleTest.waitForFocus(checkTest, win); + } + function checkTest() { + is(win.screenX, left, "The window should be placed now at x=" + left + "px"); + is(win.screenY, upper, "The window should be placed now at y=" + upper + "px"); + is(win.outerHeight, 200, "The window size should be height=200px"); + is(win.outerWidth, 200, "The window size should be width=200px"); + runTest2(); + } + function runTest2() { + win.close(); + win = window.openDialog("window_screenPosSize.xul", + null, + "chrome,dialog=no,all"); + SimpleTest.waitForFocus(checkTest2, win); + } + function checkTest2() { + let runTime = Components.classes["@mozilla.org/xre/app-info;1"] + .getService(Components.interfaces.nsIXULRuntime); + if (runTime.OS != "Linux") { + is(win.screenX, 80, "The window should be placed now at x=80px"); + is(win.screenY, 80, "The window should be placed now at y=80px"); + } + is(win.outerHeight, 300, "The window size should be height=300px"); + is(win.outerWidth, 300, "The window size should be width=300px"); + win.close(); + SimpleTest.finish(); + } +]]></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_scrollbar.xul b/toolkit/content/tests/chrome/test_scrollbar.xul new file mode 100644 index 0000000000..11dccbee94 --- /dev/null +++ b/toolkit/content/tests/chrome/test_scrollbar.xul @@ -0,0 +1,137 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for scrollbars + --> +<window title="Scrollbar" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"/> + + <hbox> + <scrollbar orient="horizontal" + id="scroller" + curpos="0" + maxpos="600" + pageincrement="400" + width="500" + style="margin:0"/> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +/** Test for Scrollbar **/ +var scrollbarTester = { + scrollbar: null, + middlePref: false, + startTest: function() { + this.scrollbar = $("scroller"); + this.middlePref = this.getMiddlePref(); + var self = this; + [0, 1, 2].map(function(button) { + [false, true].map(function(alt) { + [false, true].map(function(shift) { + self.testThumbDragging(button, alt, shift); + }) + }) + }); + SimpleTest.finish(); + }, + testThumbDragging: function(button, withAlt, withShift) { + this.reset(); + var x = 160; // on the right half of the thumb + var y = 5; + + var isMac = navigator.platform.indexOf("Mac") != -1; + var runtime = Components.classes["@mozilla.org/xre/app-info;1"] + .getService(Components.interfaces.nsIXULRuntime); + var isGtk = runtime.widgetToolkit.indexOf("gtk") != -1; + + // Start the drag. + this.mousedown(x, y, button, withAlt, withShift); + var newPos = this.getPos(); + var scrollToClick = (newPos != 0); + if (isMac || isGtk) { + ok(!scrollToClick, "On Linux and Mac OS X, clicking the scrollbar thumb "+ + "should never move it."); + } else if (button == 0 && withShift) { + ok(scrollToClick, "On platforms other than Linux and Mac OS X, holding "+ + "shift should enable scroll-to-click on the scrollbar thumb."); + } else if (button == 1 && this.middlePref) { + ok(scrollToClick, "When middlemouse.scrollbarPosition is on, clicking the "+ + "thumb with the middle mouse button should center it "+ + "around the cursor.") + } + + // Move one pixel to the right. + this.mousemove(x+1, y, button, withAlt, withShift); + var newPos2 = this.getPos(); + if (newPos2 != newPos) { + ok(newPos2 > newPos, "Scrollbar thumb should follow the mouse when dragged."); + ok(newPos2 - newPos < 3, "Scrollbar shouldn't move further than the mouse when dragged."); + ok(button == 0 || (button == 1 && this.middlePref) || (button == 2 && isGtk), + "Dragging the scrollbar should only be possible with the left mouse button."); + } else { + // Dragging had no effect. + if (button == 0) { + ok(false, "Dragging the scrollbar thumb should work."); + } else if (button == 1 && this.middlePref && (!isGtk && !isMac)) { + ok(false, "When middlemouse.scrollbarPosition is on, dragging the "+ + "scrollbar thumb should be possible using the middle mouse button."); + } else { + ok(true, "Dragging works correctly."); + } + } + + // Release the mouse button. + this.mouseup(x+1, y, button, withAlt, withShift); + var newPos3 = this.getPos(); + ok(newPos3 == newPos2, + "Releasing the mouse button after dragging the thumb shouldn't move it."); + }, + getMiddlePref: function() { + // It would be better to test with different middlePref settings, + // but the setting is only queried once, at browser startup, so + // changing it here wouldn't have any effect + var prefService = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefService); + var mouseBranch = prefService.getBranch("middlemouse."); + return mouseBranch.getBoolPref("scrollbarPosition"); + }, + setPos: function(pos) { + this.scrollbar.setAttribute("curpos", pos); + }, + getPos: function() { + return this.scrollbar.getAttribute("curpos"); + }, + reset: function() { + this.setPos(0); + }, + mousedown: function(x, y, button, alt, shift) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousedown", 'button': button, + altKey: alt, shiftKey: shift }); + }, + mousemove: function(x, y, button, alt, shift) { + synthesizeMouse(this.scrollbar, x, y, { type: "mousemove", 'button': button, + altKey: alt, shiftKey: shift }); + }, + mouseup: function(x, y, button, alt, shift) { + synthesizeMouse(this.scrollbar, x, y, { type: "mouseup", 'button': button, + altKey: alt, shiftKey: shift }); + } +} + +function doTest() { + setTimeout(function() { scrollbarTester.startTest(); }, 0); +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(doTest); + +]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_showcaret.xul b/toolkit/content/tests/chrome/test_showcaret.xul new file mode 100644 index 0000000000..75a8cf6a2b --- /dev/null +++ b/toolkit/content/tests/chrome/test_showcaret.xul @@ -0,0 +1,101 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Show Caret Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<iframe id="f1" width="100" height="100" onload="frameLoaded()" + src="data:text/html,%3Cbody%20style='height:%208000px'%3E%3Cp%3EHello%3C/p%3EGoodbye%3C/body%3E"/> +<!-- <body style='height: 8000px'><p>Hello</p><span id='s'>Goodbye<span></body> --> +<iframe id="f2" type="content" showcaret="true" width="100" height="100" onload="frameLoaded()" + src="data:text/html,%3Cbody%20style%3D%27height%3A%208000px%27%3E%3Cp%3EHello%3C%2Fp%3E%3Cspan%20id%3D%27s%27%3EGoodbye%3Cspan%3E%3C%2Fbody%3E"/> + +<script> +<![CDATA[ + +var framesLoaded = 0; +var otherWindow = null; + +function frameLoaded() { if (++framesLoaded == 2) SimpleTest.waitForFocus(runTest); } + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + var sel1 = frames[0].getSelection(); + sel1.collapse(frames[0].document.body, 0); + + var sel2 = frames[1].getSelection(); + sel2.collapse(frames[1].document.body, 0); + window.frames[0].focus(); + document.commandDispatcher.getControllerForCommand("cmd_moveBottom").doCommand("cmd_moveBottom"); + + var listener = function() { + if (!(frames[0].scrollY > 0)) { + window.content.removeEventListener("scroll", listener, false); + } + } + window.frames[0].addEventListener("scroll", listener, false); + + var sel1 = frames[0].getSelection(); + sel1.collapse(frames[0].document.body, 0); + + var sel2 = frames[1].getSelection(); + sel2.collapse(frames[1].document.body, 0); + + window.frames[0].focus(); + document.commandDispatcher.getControllerForCommand("cmd_moveBottom").doCommand("cmd_moveBottom"); + is(sel1.focusNode, frames[0].document.body, "focusNode for non-showcaret"); + is(sel1.focusOffset, 0, "focusOffset for non-showcaret"); + + window.frames[1].focus(); + document.commandDispatcher.getControllerForCommand("cmd_moveBottom").doCommand("cmd_moveBottom"); + + ok(frames[1].scrollY < + frames[1].document.getElementById('s').getBoundingClientRect().top, + "scrollY for showcaret"); + isnot(sel2.focusNode, frames[1].document.body, "focusNode for showcaret"); + ok(sel2.anchorOffset > 0, "focusOffset for showcaret"); + + otherWindow = window.open("window_showcaret.xul", "_blank", "chrome,width=400,height=200"); + otherWindow.addEventListener("focus", otherWindowFocused, false); +} + +function otherWindowFocused() +{ + otherWindow.removeEventListener("focus", otherWindowFocused, false); + + // enable caret browsing temporarily to test caret movement + var prefs = Components.classes["@mozilla.org/preferences-service;1"]. + getService(Components.interfaces.nsIPrefBranch); + prefs.setBoolPref("accessibility.browsewithcaret", true); + + var hbox = otherWindow.document.documentElement.firstChild; + hbox.focus(); + is(otherWindow.document.activeElement, hbox, "hbox in other window is focused"); + + document.commandDispatcher.getControllerForCommand("cmd_lineNext").doCommand("cmd_lineNext"); + is(otherWindow.document.activeElement, hbox, "hbox still focused in other window after down movement"); + + prefs.setBoolPref("accessibility.browsewithcaret", false); + + otherWindow.close(); + SimpleTest.finish(); +} + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_sorttemplate.xul b/toolkit/content/tests/chrome/test_sorttemplate.xul new file mode 100644 index 0000000000..a08e3b6a8a --- /dev/null +++ b/toolkit/content/tests/chrome/test_sorttemplate.xul @@ -0,0 +1,89 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<?xml-stylesheet href="data:text/css,window > |people { display: none }" type="text/css"?> +<!-- + XUL Widget Test for tabindex + --> +<window title="tabindex" width="500" height="600" + onfocus="runTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<people id="famouspeople" xmlns=""> + <person name="Napoleon Bonaparte" gender="male"/> + <person name="Cleopatra" gender="female"/> + <person name="Julius Caesar" gender="male"/> + <person name="Ferdinand Magellan" gender="male"/> + <person name="Laura Secord" gender="female"/> +</people> + +<tree id="tree" datasources="#famouspeople" ref="*" querytype="xml" flex="1"> + <treecols> + <treecol label="Name" flex="1" sort="?name"/> + <treecol label="Gender" flex="1" sort="?gender"/> + </treecols> + <template> + <query/> + <rule> + <action> + <treechildren id="treechildren-strings"> + <treeitem uri="?"> + <treerow> + <treecell label="?name"/> + <treecell label="?gender"/> + </treerow> + </treeitem> + </treechildren> + </action> + </rule> + </template> +</tree> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function runTest() +{ + var tree = $("tree"); + var col = tree.columns[0].element; + synthesizeMouse(col, 12, 2, { }); + checkRowOrder(tree, ["Cleopatra", "Ferdinand Magellan", "Julius Caesar", "Laura Secord", "Napoleon Bonaparte"], "acsending"); + + synthesizeMouse(col, 12, 2, { }); + checkRowOrder(tree, ["Napoleon Bonaparte", "Laura Secord", "Julius Caesar", "Ferdinand Magellan", "Cleopatra"], "descending"); + + synthesizeMouse(col, 12, 2, { }); + checkRowOrder(tree, ["Napoleon Bonaparte", "Laura Secord", "Julius Caesar", "Ferdinand Magellan", "Cleopatra"], "natural"); + + SimpleTest.finish(); +} + +function checkRowOrder(tree, expected, testid) +{ + var index = 0; + var item = tree.firstChild.nextSibling.nextSibling.firstChild; + while (item && index < expected.length) { + if (item.firstChild.firstChild.getAttribute("label") != expected[index++]) + break; + item = item.nextSibling; + } + ok(index == expected.length && !item, testid + " row order"); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_statusbar.xul b/toolkit/content/tests/chrome/test_statusbar.xul new file mode 100644 index 0000000000..160cc25d04 --- /dev/null +++ b/toolkit/content/tests/chrome/test_statusbar.xul @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for statusbar + --> +<window title="Statusbar Test" + onload="setTimeout(test_statusbar, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<statusbar> + <statusbarpanel id="panel" label="OK" image="happy.png"/> +</statusbar> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_statusbar() +{ + var panel = $("panel"); + ok(panel.label, "OK", "statusbarpanel label"); + ok(panel.image, "happy.png", "statusbarpanel image"); + panel.src = "sad.png"; + ok(panel.src, "sad.png", "statusbarpanel set src"); + ok(panel.getAttribute("src"), "sad.png", "statusbarpanel set src attribute"); + + SimpleTest.finish(); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_subframe_origin.xul b/toolkit/content/tests/chrome/test_subframe_origin.xul new file mode 100644 index 0000000000..bde1e49459 --- /dev/null +++ b/toolkit/content/tests/chrome/test_subframe_origin.xul @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Subframe Event Tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + +<script> + +// Added after content child widgets were removed from ui windows. Tests sub frame +// event client coordinate offsets. + +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_subframe_origin.xul", "_blank", "chrome,width=600,height=600"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_tabbox.xul b/toolkit/content/tests/chrome/test_tabbox.xul new file mode 100644 index 0000000000..3cbacb15ad --- /dev/null +++ b/toolkit/content/tests/chrome/test_tabbox.xul @@ -0,0 +1,224 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tabboxes + --> +<window title="Tabbox Test" width="500" height="600" + onload="setTimeout(test_tabbox, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="xul_selectcontrol.js"/> + +<vbox id="tabboxes"> + +<tabbox id="tabbox"> + <tabs id="tabs"> + <tab id="tab1" label="Tab 1"/> + <tab id="tab2" label="Tab 2"/> + </tabs> + <tabpanels id="tabpanels"> + <button id="panel1" label="Panel 1"/> + <button id="panel2" label="Panel 2"/> + </tabpanels> +</tabbox> + +<tabbox id="tabbox-initwithvalue"> + <tabs id="tabs-initwithvalue" value="two"> + <tab label="Tab 1" value="one"/> + <tab label="Tab 2" value="two"/> + <tab label="Tab 3" value="three"/> + </tabs> + <tabpanels id="tabpanels-initwithvalue"> + <button label="Panel 1"/> + <button label="Panel 2"/> + <button label="Panel 3"/> + </tabpanels> +</tabbox> + +<tabbox id="tabbox-initwithselected"> + <tabs id="tabs-initwithselected" value="two"> + <tab label="Tab 1" value="one"/> + <tab label="Tab 2" value="two"/> + <tab label="Tab 3" value="three" selected="true"/> + </tabs> + <tabpanels id="tabpanels-initwithselected"> + <button label="Panel 1"/> + <button label="Panel 2"/> + <button label="Panel 3"/> + </tabpanels> +</tabbox> + +</vbox> + +<tabbox id="tabbox-nofocus"> + <textbox id="textbox-extra" hidden="true"/> + <tabs> + <tab label="Tab 1" value="one"/> + <tab id="tab-nofocus" label="Tab 2" value="two"/> + </tabs> + <tabpanels> + <tabpanel> + <button id="tab-nofocus-button" label="Label"/> + </tabpanel> + <tabpanel id="tabpanel-nofocusinpaneltab"> + <label id="tablabel" value="Label"/> + </tabpanel> + </tabpanels> +</tabbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_tabbox() +{ + var tabbox = document.getElementById("tabbox"); + var tabs = document.getElementById("tabs"); + var tabpanels = document.getElementById("tabpanels"); + + test_tabbox_State(tabbox, "tabbox initial", 0, tabs.firstChild, tabpanels.firstChild); + + // check the selectedIndex property + tabbox.selectedIndex = 1; + test_tabbox_State(tabbox, "tabbox selectedIndex 1", 1, tabs.lastChild, tabpanels.lastChild); + + tabbox.selectedIndex = 2; + test_tabbox_State(tabbox, "tabbox selectedIndex 2", 1, tabs.lastChild, tabpanels.lastChild); + + // tabbox must have a selection, so setting to -1 should do nothing + tabbox.selectedIndex = -1; + test_tabbox_State(tabbox, "tabbox selectedIndex -1", 1, tabs.lastChild, tabpanels.lastChild); + + // check the selectedTab property + tabbox.selectedTab = tabs.firstChild; + test_tabbox_State(tabbox, "tabbox selected", 0, tabs.firstChild, tabpanels.firstChild); + + // setting selectedTab to null should not do anything + tabbox.selectedTab = null; + test_tabbox_State(tabbox, "tabbox selectedTab null", 0, tabs.firstChild, tabpanels.firstChild); + + // check the selectedPanel property + tabbox.selectedPanel = tabpanels.lastChild; + test_tabbox_State(tabbox, "tabbox selectedPanel", 0, tabs.firstChild, tabpanels.lastChild); + + // setting selectedPanel to null should not do anything + tabbox.selectedPanel = null; + test_tabbox_State(tabbox, "tabbox selectedPanel null", 0, tabs.firstChild, tabpanels.lastChild); + + tabbox.selectedIndex = 0; + test_tabpanels(tabpanels, tabbox); + + tabs.removeChild(tabs.firstChild); + tabs.removeChild(tabs.firstChild); + + test_tabs(tabs); + + test_tabbox_focus(); +} + +function test_tabpanels(tabpanels, tabbox) +{ + var tab = tabbox.selectedTab; + + // changing the selection on the tabpanels should not affect the tabbox + // or tabs within + // check the selectedIndex property + tabpanels.selectedIndex = 1; + test_tabbox_State(tabbox, "tabpanels tabbox selectedIndex 1", 0, tab, tabpanels.lastChild); + test_tabpanels_State(tabpanels, "tabpanels selectedIndex 1", 1, tabpanels.lastChild); + + tabpanels.selectedIndex = 0; + test_tabbox_State(tabbox, "tabpanels tabbox selectedIndex 2", 0, tab, tabpanels.firstChild); + test_tabpanels_State(tabpanels, "tabpanels selectedIndex 2", 0, tabpanels.firstChild); + + // setting selectedIndex to -1 should do nothing + tabpanels.selectedIndex = 1; + tabpanels.selectedIndex = -1; + test_tabbox_State(tabbox, "tabpanels tabbox selectedIndex -1", 0, tab, tabpanels.lastChild); + test_tabpanels_State(tabpanels, "tabpanels selectedIndex -1", 1, tabpanels.lastChild); + + // check the tabpanels.selectedPanel property + tabpanels.selectedPanel = tabpanels.lastChild; + test_tabbox_State(tabbox, "tabpanels tabbox selectedPanel", 0, tab, tabpanels.lastChild); + test_tabpanels_State(tabpanels, "tabpanels selectedPanel", 1, tabpanels.lastChild); + + // check setting the tabpanels.selectedPanel property to null + tabpanels.selectedPanel = null; + test_tabbox_State(tabbox, "tabpanels selectedPanel null", 0, tab, tabpanels.lastChild); +} + +function test_tabs(tabs) +{ + test_nsIDOMXULSelectControlElement(tabs, "tab", "tabs"); + // XXXndeakin would test the UI aspect of tabs, but the mouse + // events on tabs are fired in a timeout causing the generic + // test_nsIDOMXULSelectControlElement_UI method not to work + // test_nsIDOMXULSelectControlElement_UI(tabs, null); +} + +function test_tabbox_State(tabbox, testid, index, tab, panel) +{ + is(tabbox.selectedIndex, index, testid + " selectedIndex"); + is(tabbox.selectedTab, tab, testid + " selectedTab"); + is(tabbox.selectedPanel, panel, testid + " selectedPanel"); +} + +function test_tabpanels_State(tabpanels, testid, index, panel) +{ + is(tabpanels.selectedIndex, index, testid + " selectedIndex"); + is(tabpanels.selectedPanel, panel, testid + " selectedPanel"); +} + +function test_tabbox_focus() +{ + $("tabboxes").hidden = true; + $(document.activeElement).blur(); + + var tabbox = $("tabbox-nofocus"); + var tab = $("tab-nofocus"); + + when_tab_focused(tab, function () { + ok(document.activeElement, tab, "focus in tab with no focusable elements"); + + tabbox.selectedIndex = 0; + $("tab-nofocus-button").focus(); + + when_tab_focused(tab, function () { + ok(document.activeElement, tab, "focus in tab with no focusable elements, but with something in another tab focused"); + + var textboxExtra = $("textbox-extra"); + textboxExtra.addEventListener("focus", function () { + textboxExtra.removeEventListener("focus", arguments.callee, true); + ok(document.activeElement, textboxExtra, "focus in tab with focus currently in textbox that is sibling of tabs"); + + SimpleTest.finish(); + }, true); + + tabbox.selectedIndex = 0; + textboxExtra.hidden = false; + synthesizeMouseAtCenter(tab, { }); + }); + + synthesizeMouseAtCenter(tab, { }); + }); + + synthesizeMouseAtCenter(tab, { }); +} + +function when_tab_focused(tab, callback) { + tab.addEventListener("focus", function onFocused() { + tab.removeEventListener("focus", onFocused, true); + SimpleTest.executeSoon(callback); + }, true); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tabindex.xul b/toolkit/content/tests/chrome/test_tabindex.xul new file mode 100644 index 0000000000..425cb7b9ed --- /dev/null +++ b/toolkit/content/tests/chrome/test_tabindex.xul @@ -0,0 +1,120 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tabindex + --> +<window title="tabindex" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<!-- + Elements are navigated in the following order: + 1. tabindex > 0 in tree order + 2. tabindex = 0 in tree order + Elements with tabindex = -1 are not in the tab order + --> +<hbox> + <button id="t5" label="One"/> + <checkbox id="no1" label="Two" tabindex="-1"/> + <button id="t6" label="Three" tabindex="0"/> + <checkbox id="t1" label="Four" tabindex="1"/> +</hbox> +<hbox> + <textbox id="t7" idmod="t3" size="3"/> + <textbox id="no2" size="3" tabindex="-1"/> + <textbox id="t8" idmod="t4" size="3" tabindex="0"/> + <textbox id="t2" idmod="t1" size="3" tabindex="1"/> +</hbox> +<hbox> + <button id="no3" style="-moz-user-focus: ignore;" label="One"/> + <checkbox id="no4" style="-moz-user-focus: ignore;" label="Two" tabindex="-1"/> + <button id="t9" style="-moz-user-focus: ignore;" label="Three" tabindex="0"/> + <checkbox id="t3" style="-moz-user-focus: ignore;" label="Four" tabindex="1"/> +</hbox> +<hbox> + <textbox id="t10" idmod="t5" style="-moz-user-focus: ignore;" size="3"/> + <textbox id="no5" style="-moz-user-focus: ignore;" size="3" tabindex="-1"/> + <textbox id="t11" idmod="t6" style="-moz-user-focus: ignore;" size="3" tabindex="0"/> + <textbox id="t4" idmod="t2" style="-moz-user-focus: ignore;" size="3" tabindex="1"/> +</hbox> +<listbox id="t12" idmod="t7"> + <listitem label="Item One"/> +</listbox> + +<hbox> + <!-- the tabindex attribute does not apply to non-controls, so it + should be treated as -1 for non-focusable dropmarkers, and 0 + for focusable dropmarkers. Thus, the first four dropmarkers + are not in the tab order, and the last four dropmarkers should + be in the tab order just after the listbox above. + --> + <dropmarker id="no6"/> + <dropmarker id="no7" tabindex="-1"/> + <dropmarker id="no8" tabindex="0"/> + <dropmarker id="no9" tabindex="1"/> + <dropmarker id="t13" style="-moz-user-focus: normal;"/> + <dropmarker id="t14" style="-moz-user-focus: normal;" tabindex="-1"/> + <dropmarker id="t15" style="-moz-user-focus: normal;" tabindex="0"/> + <dropmarker id="t16" style="-moz-user-focus: normal;" tabindex="1"/> +</hbox> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gAdjustedTabFocusModel = false; +var gTestCount = 16; +var gTestsOccurred = 0; + +function runTests() +{ + var t; + window.addEventListener("focus", function (event) { + if (t == 1 && event.target.id == "t2") { + // looks to be using the MacOSX Full Keyboard Access set to Textboxes + // and lists only so use the idmod attribute instead + gAdjustedTabFocusModel = true; + gTestCount = 7; + } + + var attrcompare = gAdjustedTabFocusModel ? "idmod" : "id"; + + // check for the last test which should wrap aorund to the first item + // consider the focus event on the inner input of textboxes instead + if (event.originalTarget.localName == "input") { + is(document.getBindingParent(event.originalTarget).getAttribute(attrcompare), + "t" + t, "tab " + t + " to inner input"); + gTestsOccurred++; + } + else { + is(event.target.getAttribute(attrcompare), "t" + t, "tab " + t + " to " + event.target.localName) + if (event.target.localName != "textbox") + gTestsOccurred++; + } + }, true); + + for (t = 1; t <= gTestCount; t++) + synthesizeKey("VK_TAB", { }); + + is(gTestsOccurred, gTestCount, "test count"); + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_textbox_dictionary.xul b/toolkit/content/tests/chrome/test_textbox_dictionary.xul new file mode 100644 index 0000000000..8e69dd15e3 --- /dev/null +++ b/toolkit/content/tests/chrome/test_textbox_dictionary.xul @@ -0,0 +1,98 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for textbox with Add and Undo Add to Dictionary + --> +<window title="Textbox Add and Undo Add to Dictionary Test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + + <hbox> + <textbox id="t1" value="Hellop" oncontextmenu="runContextMenuTest()" spellcheck="true"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var textbox; +var testNum; + +function bringUpContextMenu(element) +{ + synthesizeMouseAtCenter(element, { type: "contextmenu", button: 2}); +} + +function leftClickElement(element) +{ + synthesizeMouseAtCenter(element, { button: 0 }); +} + +function startTests() +{ + textbox = document.getElementById("t1"); + textbox.focus(); + testNum = 0; + + Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); + onSpellCheck(textbox, function () { + bringUpContextMenu(textbox); + }); +} + +function runContextMenuTest() +{ + SimpleTest.executeSoon( function() { + // The textbox has its children in an hbox XUL element, so get that first + var hbox = document.getAnonymousNodes(textbox).item(0); + + var contextMenu = document.getAnonymousElementByAttribute(hbox, "anonid", "input-box-contextmenu"); + + switch(testNum) + { + case 0: // "Add to Dictionary" button + var addToDict = contextMenu.querySelector("[anonid=spell-add-to-dictionary]"); + ok(!addToDict.hidden, "Is Add to Dictionary visible?"); + + var separator = contextMenu.querySelector("[anonid=spell-suggestions-separator]"); + ok(!separator.hidden, "Is separator visible?"); + + addToDict.doCommand(); + + contextMenu.hidePopup(); + testNum++; + + onSpellCheck(textbox, function () { + bringUpContextMenu(textbox); + }); + break; + + case 1: // "Undo Add to Dictionary" button + var undoAddDict = contextMenu.querySelector("[anonid=spell-undo-add-to-dictionary]"); + ok(!undoAddDict.hidden, "Is Undo Add to Dictioanry visible?"); + + var separator = contextMenu.querySelector("[anonid=spell-suggestions-separator]"); + ok(!separator.hidden, "Is separator hidden?"); + + undoAddDict.doCommand(); + + contextMenu.hidePopup(); + onSpellCheck(textbox, function () { + SimpleTest.finish(); + }); + break; + } + }); +} + +SimpleTest.waitForFocus(startTests); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_textbox_emptytext.xul b/toolkit/content/tests/chrome/test_textbox_emptytext.xul new file mode 100644 index 0000000000..41c702a90e --- /dev/null +++ b/toolkit/content/tests/chrome/test_textbox_emptytext.xul @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for textbox with placeholder + --> +<window title="Textbox with placeholder test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <hbox> + <textbox id="t1"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function doTests() { + var t1 = $("t1"); + + t1.placeholder = 1; + ok("1" === t1.label, "placeholder exposed as label"); + ok("" === t1.value, "placeholder not exposed as value"); + + t1.label = 2; + ok("2" === t1.label, "label can be set explicitly"); + ok("1" === t1.placeholder, "placeholder persists after setting label"); + + t1.value = 3; + ok("3" === t1.value, "value setter/getter works while placeholder is present"); + ok("1" === t1.placeholder, "placeholder persists after setting value"); + + t1.value = ""; + is(t1.textLength, 0, "textLength while placeholder is displayed"); + + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(doTests); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_textbox_number.xul b/toolkit/content/tests/chrome/test_textbox_number.xul new file mode 100644 index 0000000000..369e927851 --- /dev/null +++ b/toolkit/content/tests/chrome/test_textbox_number.xul @@ -0,0 +1,353 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for textbox type="number" + --> +<window title="Textbox type='number' test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<hbox> + <textbox id="n1" type="number" size="4"/> + <textbox id="n2" type="number" value="10" min="5" max="15" wraparound="true"/> +</hbox> +<hbox> + <textbox id="n3" type="number" size="4" value="25" min="1" max="12" increment="3"/> +</hbox> +<hbox> + <textbox id="n4" type="number" size="4" value="-2" min="-8" max="18"/> + <textbox id="n5" type="number" value="-17" min="-10" max="-3"/> +</hbox> +<hbox> + <textbox id="n6" type="number" size="4" value="9" min="12" max="8"/> +</hbox> +<hbox> + <textbox id="n7" type="number" size="4" value="4.678" min="2" max="10.5" decimalplaces="2"/> + <textbox id="n8" type="number" hidespinbuttons="true"/> +</hbox> +<hbox> + <textbox id="n9" type="number" size="4" oninput="updateInputEventCount();"/> +</hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ +SimpleTest.waitForExplicitFinish(); + +// ---- NOTE: the numbers used in these tests are carefully chosen to avoid +// ---- floating point rounding issues + +function doTests() { + var n1 = $("n1"); + var n2 = $("n2"); + var n3 = $("n3"); + var n4 = $("n4"); + var n5 = $("n5"); + var n6 = $("n6"); + var n7 = $("n7"); + + testValsMinMax(n1, "initial n1", 0, 0, Infinity); + testValsMinMax(n2, "initial n2", 10, 5, 15); + testValsMinMax(n3, "initial n3", 12, 1, 12); + testValsMinMax(n4, "initial n4", -2, -8, 18); + testValsMinMax(n5, "initial n5", -10, -10, -3); + testValsMinMax(n6, "initial n6", 12, 12, 12); + testValsMinMax(n7, "initial n7", 4.68, 2, 10.5); // value should be rounded + + ok(n1.spinButtons != null && n1.spinButtons.localName == "spinbuttons", "spinButtons set"); + isnot(n1.decimalSymbol, "", "n1.decimalSymbol is set to something"); + n1.decimalSymbol = "."; + SimpleTest.is(n1.decimalSymbol, ".", "n1.decimalSymbol set to '.'"); + SimpleTest.is(n1.wrapAround, false, "wrapAround defaults to false"); + SimpleTest.is(n1.increment, 1, "increment defaults to 1"); + SimpleTest.is(n1.decimalPlaces, 0, "decimalPlaces defaults to 0"); + + SimpleTest.is(n2.wrapAround, true, "wrapAround when set to true"); + SimpleTest.is(n3.increment, 3, "increment when set to 1"); + SimpleTest.is(n7.decimalPlaces, 2, "decimalPlaces when set to 2"); + + // test changing the value + n1.value = "1700"; + testVals(n1, "set value,", 1700); + n1.value = 1600; + testVals(n1, "set value int,", 1600); + n2.value = "2"; + testVals(n2, "set value below min,", 5); + n2.value = 2; + testVals(n2, "set value below min int,", 5); + n2.value = 18; + testVals(n2, "set value above max,", 15); + n2.value = -6; + testVals(n2, "set value below min negative,", 5); + n5.value = -2; + testVals(n5, "set value above max positive,", -3); + n7.value = 5.999; + testVals(n7, "set value to decimal,", 6, "6.00"); + n7.value = "1.42"; + testVals(n7, "set value to decimal below min,", 2.00, "2.00"); + n7.value = 24.1; + testVals(n7, "set value to decimal above max,", 10.5, "10.50"); + n1.value = 4.75; + testVals(n1, "set value to decimal round,", 5); + + // test changing the valueNumber + n1.valueNumber = 27; + testVals(n1, "set valueNumber,", 27); + n2.valueNumber = 1; + testVals(n2, "set valueNumber below min,", 5); + n2.valueNumber = 77; + testVals(n2, "set valueNumber above max,", 15); + n2.valueNumber = -5; + testVals(n2, "set valueNumber below min negative,", 5); + n5.valueNumber = -8; + n5.valueNumber = -1; + testVals(n5, "set valueNumber above max positive,", -3); + n7.valueNumber = 8.23; + testVals(n7, "set valueNumber to decimal,", 8.23); + n7.valueNumber = 0.77; + testVals(n7, "set valueNumber to decimal below min,", 2.00, "2.00"); + n7.valueNumber = 29.157; + testVals(n7, "set valueNumber to decimal above max,", 10.5, "10.50"); + n1.value = 8.9; + testVals(n1, "set valueNumber to decimal round,", 9); + + // test changing the min + n1.value = 6; + n1.min = 8; + testValsMinMax(n1, "set integer min,", 8, 8, Infinity); + n7.value = 5.5; + n7.min = 6.7; + testValsMinMax(n7, "set decimal min,", 6.7, 6.7, 10.5, "6.70"); + + // test changing the max + n1.value = 25; + n1.max = 22; + testValsMinMax(n1, "set integer max,", 22, 8, 22); + n7.value = 10.2; + n7.max = 10.1; + testValsMinMax(n7, "set decimal max,", 10.1, 6.7, 10.1, "10.10"); + + // test decrease() and increase() methods + testIncreaseDecrease(n1, "integer", 1, 0, 8, 22); + testIncreaseDecrease(n7, "decimal", 1, 2, 6.7, 10.1); + testIncreaseDecrease(n3, "integer with increment", 3, 0, 1, 12); + + n7.min = 2.7; + n7.value = 10.1; + n7.increment = 4.3; + SimpleTest.is(n7.increment, 4.3, "increment changed"); + testIncreaseDecrease(n7, "integer with increment", 4.3, 2, 2.7, 10.1); + + n2.value = n2.min; + n2.decrease(); + testVals(n2, "integer wraparound decrease method", n2.max); + n2.increase(); + testVals(n2, "integer wraparound decrease method", n2.min); + + n7.wrapAround = true; + SimpleTest.is(n7.wrapAround, true, "change wrapAround"); + n7.value = n7.min + 0.01; + n7.decrease(); + testVals(n7, "decimal wraparound decrease method", n7.max, n7.max.toFixed(2)); + n7.increase(); + testVals(n7, "decimal wraparound decrease method", n7.min, n7.min.toFixed(2)); + + n1.value = 22; + n1.decimalPlaces = 3; + testVals(n1, "set decimalPlaces 3", 22, "22.000"); + n1.value = 10.624; + testVals(n1, "set decimalPlaces 3 set value,", 10.624); + n1.decimalPlaces = 0; + testVals(n1, "set decimalPlaces 0 set value,", 11); + n1.decimalPlaces = Infinity; + n1.value = 10.678123; + testVals(n1, "set decimalPlaces Infinity set value,", 10.678123); + + n1.decimalSymbol = ","; + SimpleTest.is(n1.decimalSymbol, ",", "n1.decimalSymbol set to ','"); + n1.value = "9.67"; + testVals(n1, "set decimalPlaces set value,", 9.67); + + n1.decimalSymbol = "."; + SimpleTest.is(n1.decimalSymbol, ".", "n1.decimalSymbol set back to '.'"); + n1.decimalPlaces = 0; + + // UI tests + n1.min = 5; + n1.max = 15; + n1.value = 5; + n1.focus(); + + var sb = n1.spinButtons; + var sbbottom = sb.getBoundingClientRect().bottom - sb.getBoundingClientRect().top - 2; + + synthesizeKey("VK_UP", {}); + testVals(n1, "key up", 6); + + synthesizeKey("VK_DOWN", {}); + testVals(n1, "key down", 5); + + synthesizeMouse(sb, 2, 2, {}); + testVals(n1, "spinbuttons up", 6); + synthesizeMouse(sb, 2, sbbottom, {}); + testVals(n1, "spinbuttons down", 5); + + n1.value = 15; + synthesizeKey("VK_UP", {}); + testVals(n1, "key up at max", 15); + synthesizeMouse(sb, 2, 2, {}); + testVals(n1, "spinbuttons up at max", 15); + + n1.value = 5; + synthesizeKey("VK_DOWN", {}); + testVals(n1, "key down at min", 5); + synthesizeMouse(sb, 2, sbbottom, {}); + testVals(n1, "spinbuttons down at min", 5); + + n1.wrapAround = true; + n1.value = 15; + synthesizeKey("VK_UP", {}); + testVals(n1, "key up wraparound at max", 5); + n1.value = 5; + synthesizeKey("VK_DOWN", {}); + testVals(n1, "key down wraparound at min", 15); + + n1.value = 15; + synthesizeMouse(sb, 2, 2, {}); + testVals(n1, "spinbuttons up wraparound at max", 5); + n1.value = 5; + synthesizeMouse(sb, 2, sbbottom, {}); + testVals(n1, "spinbuttons down wraparound at min", 15); + + // check read only state + n1.readOnly = true; + n1.min = -10; + n1.max = 15; + n1.value = 12; + // no events should fire and no changes should occur when the field is read only + synthesizeKeyExpectEvent("VK_UP", { }, n1, "!change", "key up read only"); + is(n1.value, "12", "key up read only value"); + synthesizeKeyExpectEvent("VK_DOWN", { }, n1, "!change", "key down read only"); + is(n1.value, "12", "key down read only value"); + + synthesizeMouseExpectEvent(sb, 2, 2, { }, n1, "!change", "mouse up read only"); + is(n1.value, "12", "mouse up read only value"); + synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, n1, "!change", "mouse down read only"); + is(n1.value, "12", "mouse down read only value"); + + n1.readOnly = false; + n1.disabled = true; + synthesizeMouseExpectEvent(sb, 2, 2, { }, n1, "!change", "mouse up disabled"); + is(n1.value, "12", "mouse up disabled value"); + synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, n1, "!change", "mouse down disabled"); + is(n1.value, "12", "mouse down disabled value"); + + var nsbrect = $("n8").spinButtons.getBoundingClientRect(); + ok(nsbrect.left == 0 && nsbrect.top == 0 && nsbrect.right == 0, nsbrect.bottom == 0, + "hidespinbuttons"); + + var n9 = $("n9"); + is(n9.value, "0", "initial value"); + n9.select(); + synthesizeKey("4", {}); + is(inputEventCount, 1, "input event count"); + is(inputEventValue, "4", "input value"); + is(n9.value, "4", "updated value"); + synthesizeKey("2", {}); + is(inputEventCount, 2, "input event count"); + is(inputEventValue, "42", "input value"); + is(n9.value, "42", "updated value"); + synthesizeKey("VK_BACK_SPACE", {}); + is(inputEventCount, 3, "input event count"); + is(inputEventValue, "4", "input value"); + is(n9.value, "4", "updated value"); + synthesizeKey("A", {accelKey: true}); + synthesizeKey("VK_DELETE", {}); + is(inputEventCount, 4, "input event count"); + is(inputEventValue, "0", "input value"); + is(n9.value, "0", "updated value"); + + SimpleTest.finish(); +} + +var inputEventCount = 0; +var inputEventValue = null; +function updateInputEventCount() { + inputEventValue = $("n9").value; + inputEventCount++; +}; + +function testVals(nb, name, valueNumber, valueFieldNumber) { + if (valueFieldNumber === undefined) + valueFieldNumber = "" + valueNumber; + + SimpleTest.is(nb.value, "" + valueNumber, name + " value is '" + valueNumber + "'"); + SimpleTest.is(nb.valueNumber, valueNumber, name + " valueNumber is " + valueNumber); + + // This value format depends on the localized decimal symbol. + var localizedValue = valueFieldNumber.replace(/\./, nb.decimalSymbol); + SimpleTest.is(nb.inputField.value, localizedValue, + name + " inputField value is '" + localizedValue + "'"); +} + +function testValsMinMax(nb, name, valueNumber, min, max, valueFieldNumber) { + testVals(nb, name, valueNumber, valueFieldNumber); + SimpleTest.is(nb.min, min, name + " min is " + min); + SimpleTest.is(nb.max, max, name + " max is " + max); +} + +function testIncreaseDecrease(nb, testid, increment, fixedCount, min, max) +{ + testid += " "; + + nb.value = max; + nb.decrease(); + testVals(nb, testid + "decrease method", max - increment, + (max - increment).toFixed(fixedCount)); + nb.increase(); + testVals(nb, testid + "increase method", max, max.toFixed(fixedCount)); + nb.value = min; + nb.decrease(); + testVals(nb, testid + "decrease method at min", min, min.toFixed(fixedCount)); + nb.value = max; + nb.increase(); + testVals(nb, testid + "increase method at max", max, max.toFixed(fixedCount)); + + nb.focus(); + nb.value = min; + + // pressing the cursor up and down keys should adjust the value + synthesizeKeyExpectEvent("VK_UP", { }, nb, "change", testid + "key up"); + is(nb.value, String(min + increment), testid + "key up"); + nb.value = max; + synthesizeKeyExpectEvent("VK_UP", { }, nb, "!change", testid + "key up at max"); + is(nb.value, String(max), testid + "key up at max"); + synthesizeKeyExpectEvent("VK_DOWN", { }, nb, "change", testid + "key down"); + is(nb.value, String(max - increment), testid + "key down"); + nb.value = min; + synthesizeKeyExpectEvent("VK_DOWN", { }, nb, "!change", testid + "key down at min"); + is(nb.value, String(min), testid + "key down at min"); + + // check pressing the spinbutton arrows + var sb = nb.spinButtons; + var sbbottom = sb.getBoundingClientRect().bottom - sb.getBoundingClientRect().top - 2; + nb.value = min; + synthesizeMouseExpectEvent(sb, 2, 2, { }, nb, "change", testid + "mouse up"); + is(nb.value, String(min + increment), testid + "mouse up"); + nb.value = max; + synthesizeMouseExpectEvent(sb, 2, 2, { }, nb, "!change", testid + "mouse up at max"); + synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, nb, "change", testid + "mouse down"); + is(nb.value, String(max - increment), testid + "mouse down"); + nb.value = min; + synthesizeMouseExpectEvent(sb, 2, sbbottom, { }, nb, "!change", testid + "mouse down at min"); +} + +SimpleTest.waitForFocus(doTests); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_textbox_search.xul b/toolkit/content/tests/chrome/test_textbox_search.xul new file mode 100644 index 0000000000..ae61533619 --- /dev/null +++ b/toolkit/content/tests/chrome/test_textbox_search.xul @@ -0,0 +1,170 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for search textbox + --> +<window title="Search textbox test" width="500" height="600" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <hbox> + <textbox id="searchbox" + type="search" + oncommand="doSearch(this.value);" + placeholder="random placeholder" + timeout="1"/> + </hbox> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gExpectedValue; +var gLastTest; + +function doTests() { + var textbox = $("searchbox"); + var icons = document.getAnonymousElementByAttribute(textbox, "anonid", "search-icons"); + var searchIcon = document.getAnonymousElementByAttribute(textbox, "class", "textbox-search-icon"); + var clearIcon = document.getAnonymousElementByAttribute(textbox, "class", "textbox-search-clear"); + + ok(icons, "icon deck found"); + ok(searchIcon, "search icon found"); + ok(clearIcon, "clear icon found"); + is(icons.selectedPanel, searchIcon, "search icon is displayed"); + + is(textbox.placeholder, "random placeholder", "search textbox supports placeholder"); + is(textbox.value, "", "placeholder doesn't interfere with the real value"); + + function iconClick(aIcon) { + is(icons.selectedPanel, aIcon, aIcon.className + " icon must be displayed in order to be clickable"); + + //XXX synthesizeMouse worked on Linux but failed on Windows an Mac + // for unknown reasons. Manually dispatch the event for now. + //synthesizeMouse(aIcon, 0, 0, {}); + + var event = document.createEvent("MouseEvent"); + event.initMouseEvent("click", true, true, window, 1, + 0, 0, 0, 0, + false, false, false, false, + 0, null); + aIcon.dispatchEvent(event); + } + + iconClick(searchIcon); + is(textbox.getAttribute("focused"), "true", "clicking the search icon focuses the textbox"); + + textbox.value = "foo"; + is(icons.selectedPanel, clearIcon, "clear icon is displayed when setting a value"); + + textbox.reset(); + is(textbox.defaultValue, "", "defaultValue is empty"); + is(textbox.value, "", "reset method clears the textbox"); + is(icons.selectedPanel, searchIcon, "search icon is displayed after textbox.reset()"); + + textbox.value = "foo"; + gExpectedValue = ""; + iconClick(clearIcon); + is(textbox.value, "", "clicking the clear icon clears the textbox"); + ok(gExpectedValue == null, "search triggered when clearing the textbox with the clear icon"); + + textbox.value = "foo"; + gExpectedValue = ""; + synthesizeKey("VK_ESCAPE", {}); + is(textbox.value, "", "escape key clears the textbox"); + ok(gExpectedValue == null, "search triggered when clearing the textbox with the escape key"); + + textbox.value = "bar"; + gExpectedValue = "bar"; + textbox.doCommand(); + ok(gExpectedValue == null, "search triggered with doCommand"); + + gExpectedValue = "bar"; + synthesizeKey("VK_RETURN", {}); + ok(gExpectedValue == null, "search triggered with enter key"); + + textbox.value = ""; + textbox.searchButton = true; + is(textbox.getAttribute("searchbutton"), "true", "searchbutton attribute set on the textbox"); + is(searchIcon.getAttribute("searchbutton"), "true", "searchbutton attribute inherited to the search icon"); + + textbox.value = "foo"; + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode if there's a value"); + + gExpectedValue = "foo"; + iconClick(searchIcon); + ok(gExpectedValue == null, "search triggered when clicking the search icon in search button mode"); + is(icons.selectedPanel, clearIcon, "clear icon displayed in search button mode after submitting"); + + synthesizeKey("o", {}); + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode when typing a key"); + + gExpectedValue = "fooo"; + iconClick(searchIcon); // display the clear icon (tested above) + + textbox.value = "foo"; + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode when the value is changed"); + + gExpectedValue = "foo"; + synthesizeKey("VK_RETURN", {}); + ok(gExpectedValue == null, "search triggered with enter key in search button mode"); + is(icons.selectedPanel, clearIcon, "clear icon displayed in search button mode after submitting with enter key"); + + textbox.value = "x"; + synthesizeKey("VK_BACK_SPACE", {}); + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode when deleting the value with the backspace key"); + + gExpectedValue = ""; + synthesizeKey("VK_RETURN", {}); + ok(gExpectedValue == null, "search triggered with enter key in search button mode"); + is(icons.selectedPanel, searchIcon, "search icon displayed in search button mode after submitting an empty string"); + + textbox.readOnly = true; + gExpectedValue = "foo"; + textbox.value = "foo"; + iconClick(searchIcon); + ok(gExpectedValue == null, "search triggered when clicking the search icon in search button mode while the textbox is read-only"); + is(icons.selectedPanel, searchIcon, "search icon persists in search button mode after submitting while the textbox is read-only"); + textbox.readOnly = false; + + textbox.disabled = true; + is(searchIcon.getAttribute("disabled"), "true", "disabled attribute inherited to the search icon"); + is(clearIcon.getAttribute("disabled"), "true", "disabled attribute inherited to the clear icon"); + gExpectedValue = false; + textbox.value = "foo"; + iconClick(searchIcon); + ok(gExpectedValue == false, "search *not* triggered when clicking the search icon in search button mode while the textbox is disabled"); + is(icons.selectedPanel, searchIcon, "search icon persists in search button mode when trying to submit while the textbox is disabled"); + textbox.disabled = false; + ok(!searchIcon.hasAttribute("disabled"), "disabled attribute removed from the search icon"); + ok(!clearIcon.hasAttribute("disabled"), "disabled attribute removed from the clear icon"); + + textbox.searchButton = false; + ok(!textbox.hasAttribute("searchbutton"), "searchbutton attribute removed from the textbox"); + ok(!searchIcon.hasAttribute("searchbutton"), "searchbutton attribute removed from the search icon"); + + gLastTest = true; + gExpectedValue = "123"; + textbox.value = "1"; + synthesizeKey("2", {}); + synthesizeKey("3", {}); +} + +function doSearch(aValue) { + is(aValue, gExpectedValue, "search triggered with expected value"); + gExpectedValue = null; + if (gLastTest) + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(doTests); + + ]]></script> + +</window> diff --git a/toolkit/content/tests/chrome/test_timepicker.xul b/toolkit/content/tests/chrome/test_timepicker.xul new file mode 100644 index 0000000000..98e370137f --- /dev/null +++ b/toolkit/content/tests/chrome/test_timepicker.xul @@ -0,0 +1,207 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for timepicker + --> +<window title="timepicker" width="500" height="600" + onload="setTimeout(testtag_timepicker, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<timepicker id="timepicker"/> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function testtag_timepicker() +{ + var tp = document.getElementById("timepicker"); + + var testid = "timepicker "; + + var today = new Date(); + var thour = today.getHours(); + var tminute = today.getMinutes(); + var tsecond = today.getSeconds(); + + // testtag_comparetime(tp, testid + "initial", thour, tminute, tsecond); + + // check that setting the value property works + tp.value = testtag_gettimestring(thour, tminute, tsecond); + testtag_comparetime(tp, testid + "set value", thour, tminute, tsecond); + + var numberOrder = /^(\D*)\s*(\d+)(\D*)(\d+)(\D*)(\d+)\s*(\D*)$/; + var locale = Intl.DateTimeFormat().resolvedOptions().locale + "-u-ca-gregory-nu-latn"; + var fdt = new Date(2000,0,1,16,7,9).toLocaleTimeString(locale); + is(tp.is24HourClock, Number(fdt.match(numberOrder)[2]) > 12, "is24HourClock"); + + // check that setting the dateValue property works + tp.dateValue = today; + testtag_comparetime(tp, testid + "set dateValue", thour, tminute, tsecond); + ok(tp.value !== today, testid + " set dateValue different time"); + + ok(!tp.readOnly, testid + "readOnly"); + tp.readOnly = true; + ok(tp.readOnly, testid + "set readOnly"); + tp.readOnly = false; + ok(!tp.readOnly, testid + "clear readOnly"); + + function setTimeField(field, value, expectException, + expectedHour, expectedMinute, expectedSecond) + { + var exh = false; + try { + tp[field] = value; + } catch (ex) { exh = true; } + is(exh, expectException, testid + "set " + field + " " + value); + testtag_comparetime(tp, testid + "set " + field + " " + value, + expectedHour, expectedMinute, expectedSecond); + } + + // check the value property + setTimeField("value", "0:0:0", false, 0, 0, 0); + setTimeField("value", "21:1:40", false, 21, 1, 40); + setTimeField("value", "7:11:8", false, 7, 11, 8); + setTimeField("value", "04:07:02", false, 4, 7, 2); + setTimeField("value", "10:42:20", false, 10, 42, 20); + + // check that the hour, minute and second fields can be set properly + setTimeField("hour", 7, false, 7, 42, 20); + setTimeField("hour", 0, false, 0, 42, 20); + setTimeField("hour", 21, false, 21, 42, 20); + setTimeField("hour", -1, true, 21, 42, 20); + setTimeField("hour", 24, true, 21, 42, 20); + + setTimeField("minute", 0, false, 21, 0, 20); + setTimeField("minute", 9, false, 21, 9, 20); + setTimeField("minute", 10, false, 21, 10, 20); + setTimeField("minute", 35, false, 21, 35, 20); + setTimeField("minute", -1, true, 21, 35, 20); + setTimeField("minute", 60, true, 21, 35, 20); + + setTimeField("second", 0, false, 21, 35, 0); + setTimeField("second", 9, false, 21, 35, 9); + setTimeField("second", 10, false, 21, 35, 10); + setTimeField("second", 51, false, 21, 35, 51); + setTimeField("second", -1, true, 21, 35, 51); + setTimeField("second", 60, true, 21, 35, 51); + + // check when seconds is not specified + setTimeField("value", "06:05", false, 6, 5, 0); + setTimeField("value", "06:15", false, 6, 15, 0); + setTimeField("value", "16:15", false, 16, 15, 0); + + // check that times overflow properly + setTimeField("value", "5:65:21", false, 6, 5, 21); + setTimeField("value", "5:25:72", false, 5, 26, 12); + + // check invalid values for the value and dateValue properties + tp.value = "14:25:48"; + setTimeField("value", "", true, 14, 25, 48); + setTimeField("value", "1:5:6:6", true, 14, 25, 48); + setTimeField("value", "2:a:19", true, 14, 25, 48); + setTimeField("dateValue", "none", true, 14, 25, 48); + + // check the fields + ok(tp.hourField instanceof HTMLInputElement, testid + "hourField"); + ok(tp.minuteField instanceof HTMLInputElement, testid + "minuteField"); + ok(tp.secondField instanceof HTMLInputElement, testid + "secondField"); + + testtag_timepicker_UI(tp, testid); + + tp.readOnly = true; + + // check that keyboard usage doesn't change the value when the timepicker + // is read only + testtag_timepicker_UI_key(tp, testid + "readonly ", "14:25:48", + tp.hourField, 14, 25, 48, 14, 25, 48); + testtag_timepicker_UI_key(tp, testid + "readonly ", "14:25:48", + tp.minuteField, 14, 25, 48, 14, 25, 48); + testtag_timepicker_UI_key(tp, testid + "readonly ", "14:25:48", + tp.secondField, 14, 25, 48, 14, 25, 48); + + SimpleTest.finish(); +} + +function testtag_timepicker_UI(tp, testid) +{ + testid += "UI"; + + // test adjusting the time with the up and down keys + testtag_timepicker_UI_key(tp, testid, "0:12:25", tp.hourField, 1, 12, 25, 0, 12, 25); + testtag_timepicker_UI_key(tp, testid, "11:12:25", tp.hourField, 12, 12, 25, 11, 12, 25); + testtag_timepicker_UI_key(tp, testid, "7:12:25", tp.hourField, 8, 12, 25, 7, 12, 25); + testtag_timepicker_UI_key(tp, testid, "16:12:25", tp.hourField, 17, 12, 25, 16, 12, 25); + testtag_timepicker_UI_key(tp, testid, "23:12:25", tp.hourField, 0, 12, 25, 23, 12, 25); + + testtag_timepicker_UI_key(tp, testid, "15:23:46", tp.minuteField, 15, 24, 46, 15, 23, 46); + testtag_timepicker_UI_key(tp, testid, "15:0:46", tp.minuteField, 15, 1, 46, 15, 0, 46); + testtag_timepicker_UI_key(tp, testid, "15:59:46", tp.minuteField, 15, 0, 46, 15, 59, 46); + + testtag_timepicker_UI_key(tp, testid, "11:50:46", tp.secondField, 11, 50, 47, 11, 50, 46); + testtag_timepicker_UI_key(tp, testid, "11:50:0", tp.secondField, 11, 50, 1, 11, 50, 0); + testtag_timepicker_UI_key(tp, testid, "11:50:59", tp.secondField, 11, 50, 0, 11, 50, 59); +} + +function testtag_timepicker_UI_key(tp, testid, value, field, + uhour, uminute, usecond, + dhour, dminute, dsecond) +{ + tp.value = value; + field.focus(); + + var eventTarget = tp.readOnly ? null : tp; + + var testname = testid + " " + value + " key up"; + synthesizeKeyExpectEvent("VK_UP", { }, eventTarget, "change", testname); + testtag_comparetime(tp, testname, uhour, uminute, usecond); + + testname = testid + " " + value + " key down"; + synthesizeKeyExpectEvent("VK_DOWN", { }, eventTarget, "change", testname); + testtag_comparetime(tp, testname, dhour, dminute, dsecond); +} + +function testtag_gettimestring(hour, minute, second) +{ + if (minute < 10) + minute = "0" + minute; + if (second < 10) + second = "0" + second; + return hour + ":" + minute + ":" + second; +} + +function testtag_comparetime(tp, testid, hour, minute, second) +{ + is(tp.value, testtag_gettimestring(hour, minute, second), testid + " value"); + is(tp.getAttribute("value"), + testtag_gettimestring(hour, minute, second), + testid + " value attribute"); + + var dateValue = tp.dateValue; + ok(dateValue.getHours() == hour && + dateValue.getMinutes() == minute && + dateValue.getSeconds() == second, + testid + " dateValue"); + + is(tp.hour, hour, testid + " hour"); + is(tp.minute, minute, testid + " minute"); + is(tp.second, second, testid + " second"); +} + +]]> + +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_titlebar.xul b/toolkit/content/tests/chrome/test_titlebar.xul new file mode 100644 index 0000000000..d48e04a129 --- /dev/null +++ b/toolkit/content/tests/chrome/test_titlebar.xul @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for the titlebar element and window dragging + --> +<window title="Titlebar" width="200" height="200" + onload="setTimeout(test_titlebar, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +function test_titlebar() +{ + window.open("window_titlebar.xul", "_blank", "chrome,left=200,top=200"); +} + +function done(testWindow) +{ + testWindow.close(); + SimpleTest.finish(); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_toolbar.xul b/toolkit/content/tests/chrome/test_toolbar.xul new file mode 100644 index 0000000000..7adaa6aa33 --- /dev/null +++ b/toolkit/content/tests/chrome/test_toolbar.xul @@ -0,0 +1,227 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Toolbar" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="startTest();"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <toolbox id="toolbox"> + <toolbarpalette> + <toolbarbutton id="p1" label="p1"/> + <toolbarbutton id="p2" label="p2"/> + <toolbarbutton id="p3" label="p3"/> + <toolbarbutton id="p4" label="p4"/> + <toolbarbutton id="p5" label="p5"/> + <toolbarbutton id="p6" label="p6"/> + <toolbarbutton id="p7" label="p7"/> + <toolbarbutton id="p8" label="p8"/> + <toolbarbutton id="p9" label="p9"/> + <toolbarbutton id="p10" label="p10"/> + <toolbarbutton id="p11" label="p11"/> + <toolbarbutton id="p12" label="p12"/> + </toolbarpalette> + + <toolbar id="tb1" defaultset="p1,p2"/> + <toolbar id="tb2" defaultset="p4,p3"/> + <toolbar id="tb3" defaultset="p5,p6,t31"> + <toolbarbutton id="t31" label="t31" removable="true"/> + </toolbar> + <toolbar id="tb4" defaultset="t41,p7,p8"> + <toolbarbutton id="t41" label="t41" removable="true"/> + </toolbar> + <toolbar id="tb5" defaultset="p9,t51,p10"> + <toolbarbutton id="t51" label="t51" removable="true"/> + </toolbar> + + <toolbar id="tb-test" defaultset="p11,p12"/> + <toolbar id="tb-test2" defaultset=""/> + <!-- fixed toolbarbuttons always have 'fixed' in their id --> + <toolbar id="tb-test3" defaultset=""> + <toolbarbutton id="tb-fixed-1" label="tb-test3-1"/> + <toolbarbutton id="tb-fixed-2" label="tb-test3-2" removable="false"/> + <toolbarbutton id="tb-fixed-3" label="tb-test3-3"/> + </toolbar> + </toolbox> + + <toolbar id="notoolbox"/> + + <!-- test resuls are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" + style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="text/javascript"><![CDATA[ + const SPACER = /^spacer\d+/; + const SEPARATOR = /^separator\d+/; + const SPRING = /^spring\d+/; + + function testSet(aTb, aIDs, aResultIDs, aUseFixed) { + // build a list of the fixed items in the order they appear on the toolbar + var fixedSet = []; + if (aUseFixed) { + for (let i = 0; i < aTb.childNodes.length; i++) { + var id = aTb.childNodes[i].id; + if (id.indexOf("fixed") >= 0) + fixedSet.push(id); + } + } + + var currentSet = aIDs.join(","); + ok(currentSet, "setting currentSet: " + currentSet); + aTb.currentSet = currentSet; + var resultIDs = aResultIDs || aIDs; + checkSet(aTb, resultIDs, fixedSet); + } + + var checkSetCount = 0; + function checkSet(aTb, aResultIDs, aFixedSet) { + checkSetCount++; + var testID = "checkSet(" + checkSetCount + ") "; + + for (let i = 0; i < aTb.childNodes.length; i++) { + let id = aTb.childNodes[i].id; + if (aResultIDs[i] instanceof RegExp) { + ok(aResultIDs[i].test(id), + testID + "correct ID " + aResultIDs[i] + " for toolbar " + aTb.id + "; got: " + id); + } + else if (aResultIDs[i] == "*") { + is(id, aFixedSet.shift(), testID + "is fixed with ID " + id + " for toolbar " + aTb.id); + } + else { + is(id, aResultIDs[i], + testID + "correct ID " + aResultIDs[i] + " for toolbar " + aTb.id + + "****" + aResultIDs + "," + i + ","); + // remove the item from the fixed set once found + if (aFixedSet && id.indexOf("fixed") >= 0) + aFixedSet.splice(aFixedSet.indexOf(id), 1); + } + } + + if (aFixedSet) + is(aFixedSet.length, 0, testID + "extra fixed items for " + aTb.id); + is(aTb.childNodes.length, aResultIDs.length, + testID + "correct number of children for " + aTb.id); + } + + function test_defaultSet() { + checkSet($("tb1"), ["p1", "p2"]); + checkSet($("tb2"), ["p4", "p3"]); + checkSet($("tb3"), ["p5", "p6", "t31"]); + checkSet($("tb4"), ["t41", "p7", "p8"]); + checkSet($("tb5"), ["p9", "t51", "p10"]); + } + + function test_currentSet(aTb) { + ok(aTb, "have toolbar"); + var defaultSet = aTb.getAttribute("defaultset"); + var setLength = (defaultSet && defaultSet.split(",").length) || 0; + is(setLength, aTb.childNodes.length, "correct # of children initially"); + + var emptySet = [["__empty"], []]; + var testSets = [ + emptySet, + [["p11"]], + [["p11","p12"]], + [["p11","p12","bogus"], ["p11","p12"]], + [["p11"]], + emptySet, + [["spacer"], [SPACER]], + [["spring"], [SPRING]], + [["separator"], [SEPARATOR]], + [["p11", "p11", "p12", "spacer", "p11"], ["p11", "p12", SPACER]], + [["separator", "separator", "p11", "spring", "spacer"], + [SEPARATOR, SEPARATOR, "p11", SPRING, SPACER]], + [["separator", "spacer", "separator", "p11", "spring", "spacer", "p12", "spring"], + [SEPARATOR, SPACER, SEPARATOR, "p11", SPRING, SPACER, "p12", SPRING]], + emptySet + ]; + + cycleSets(aTb, testSets, emptySet, false); + } + + function test_currentSet_nonremovable() { + var tb = $("tb-test3"); + ok(tb, "have tb-test-3"); + + // the * used in the tests below means that any fixed item can appear in that position + var emptySet = [["__empty"], ["*", "*", "*"]]; + var testSets = [ + [["p1", "tb-fixed-1", "p2"], + ["p1", "tb-fixed-1", "p2", "*", "*"]], + [["p1", "tb-fixed-2", "p2"], + ["p1", "tb-fixed-2", "p2", "*", "*"]], + [["p1", "tb-fixed-3", "p2"], + ["p1", "tb-fixed-3", "p2", "*", "*"]], + emptySet, + + [["tb-fixed-1", "tb-fixed-2", "tb-fixed-3"], + ["tb-fixed-1", "tb-fixed-2", "tb-fixed-3"]], + [["tb-fixed-3", "tb-fixed-2", "tb-fixed-1"], + ["tb-fixed-3", "tb-fixed-2", "tb-fixed-1"]], + + [["tb-fixed-1", "tb-fixed-2", "tb-fixed-3", "p1", "p2"], + ["tb-fixed-1", "tb-fixed-2", "tb-fixed-3", "p1", "p2"]], + + [["tb-fixed-1", "p2", "p1"], + ["tb-fixed-1", "p2", "p1", "*", "*"]], + + [["tb-fixed-1", "p2"], + ["tb-fixed-1", "p2", "*", "*"]], + + [["p1", "p2"], ["p1", "p2", "*", "*", "*"]], + [["p2", "p1"], ["p2", "p1", "*", "*", "*"]], + + [["tb-fixed-3", "spacer", "p1"], + ["tb-fixed-3", SPACER, "p1", "*", "*"]] + ]; + + cycleSets(tb, testSets, emptySet, true); + } + + function cycleSets(aTb, aSets, aEmptySet, aUseFixed) { + // Since a lot of the tricky cases handled in the currentSet setter + // depend on going from one state to another, run through the test set + // multiple times in different orders. + var length = aSets.length; + + for (var i = 0; i < length; i++) { + testSet(aTb, aSets[i][0], aSets[i][1], aUseFixed); + } + for (var i = length - 1; i >= 0; i--) { + testSet(aTb, aSets[i][0], aSets[i][1], aUseFixed); + } + for (var i = 0; i < length; i++) { + testSet(aTb, aSets[i][0], aSets[i][1], aUseFixed); + testSet(aTb, aSets[length - i - 1][0], aSets[length - i - 1][1], aUseFixed); + testSet(aTb, aSets[i][0], aSets[i][1], aUseFixed); + testSet(aTb, aSets[i][0], aSets[i][1], aUseFixed); + } + for (var i = 0; i < length; i++) { + testSet(aTb, aEmptySet[0], aEmptySet[1], aUseFixed); + testSet(aTb, aSets[i][0], aSets[i][1], aUseFixed); + } + } + + SimpleTest.waitForExplicitFinish(); + function startTest() { + test_defaultSet(); + test_currentSet($("tb-test")); + test_currentSet($("tb-test2")); + test_currentSet_nonremovable(); + + var toolbox = $("toolbox"); + var toolbars = document.getElementsByTagName("toolbar"); + for (var t = 0; t < toolbars.length; t++) { + var toolbar = toolbars[t]; + is(toolbar.toolbox, toolbar.id == "notoolbox" ? null : toolbox, + "toolbar " + toolbar.id + " has correct toolbox"); + } + + $("tb1").toolbox = document.documentElement; + is($("tb1").toolbox, toolbox, "toolbox still correct after set"); + SimpleTest.finish(); + } + ]]></script> +</window> diff --git a/toolkit/content/tests/chrome/test_tooltip.xul b/toolkit/content/tests/chrome/test_tooltip.xul new file mode 100644 index 0000000000..b5650352da --- /dev/null +++ b/toolkit/content/tests/chrome/test_tooltip.xul @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tooltip Tests" + onload="setTimeout(runTest, 0)" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_tooltip.xul", "_blank", "chrome,width=700,height=700"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_tooltip_noautohide.xul b/toolkit/content/tests/chrome/test_tooltip_noautohide.xul new file mode 100644 index 0000000000..979e424774 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tooltip_noautohide.xul @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tooltip Noautohide Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<tooltip id="thetooltip" noautohide="true" + onpopupshown="setTimeout(tooltipStillShown, 6000)" + onpopuphidden="ok(gChecked, 'tooltip did not hide'); SimpleTest.finish()"> + <label id="label" value="This is a tooltip"/> +</tooltip> + +<button id="button" label="Tooltip Text" tooltip="thetooltip"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var gChecked = false; + +function runTests() +{ + var button = document.getElementById("button"); + var windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIDOMWindowUtils); + windowUtils.disableNonTestMouseEvents(true); + synthesizeMouse(button, 2, 2, { type: "mouseover" }); + synthesizeMouse(button, 4, 4, { type: "mousemove" }); + synthesizeMouse(button, 6, 6, { type: "mousemove" }); + windowUtils.disableNonTestMouseEvents(false); +} + +function tooltipStillShown() +{ + gChecked = true; + document.getElementById("thetooltip").hidePopup(); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.waitForFocus(runTests); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree.xul b/toolkit/content/tests/chrome/test_tree.xul new file mode 100644 index 0000000000..52de74e462 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree.xul @@ -0,0 +1,84 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tree using multiple row selection + --> +<window title="Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-simple', 'treechildren-simple', 'multiple', 'simple', 'tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-simple" rows="4"> + <treecols> + <treecol id="name" label="Name" sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" flex="1"/> + </treecols> + <treechildren id="treechildren-simple"> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> + diff --git a/toolkit/content/tests/chrome/test_tree_hier.xul b/toolkit/content/tests/chrome/test_tree_hier.xul new file mode 100644 index 0000000000..d1a599eaa1 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_hier.xul @@ -0,0 +1,136 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for hierarchical tree + --> +<window title="Hierarchical Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-hier', 'treechildren-hier', 'multiple', '', 'hierarchical tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-hier" rows="4"> + <treecols> + <treecol id="name" label="Name" primary="true" + sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" flex="2"/> + <treecol id="planet" label="Planet" flex="1"/> + <treecol id="gender" label="Gender" flex="1" cycler="true"/> + </treecols> + <treechildren id="treechildren-hier"> + <treeitem> + <treerow properties="firstrow"> + <treecell label="Mary" value="mary" properties="firstname"/> + <treecell label="206 Garden Avenue" value="206ga"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell/> + <treecell value="19ms"/> + <treecell label="Earth"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem container="true"> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + <treecell label="Saturn"/> + <treecell label="Female" value="f"/> + </treerow> + <treechildren> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + <treecell label="Female" value="f"/> + <treecell label="Neptune"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + <treecell label="Omicron Persei 8"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell label="Neptune"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + </treechildren> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue" selectable="false"/> + <treecell label=""/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + <treecell label="Neptune"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell label="Mars"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree_hier_cell.xul b/toolkit/content/tests/chrome/test_tree_hier_cell.xul new file mode 100644 index 0000000000..29e92ba3b6 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_hier_cell.xul @@ -0,0 +1,136 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for cell selection tree + --> +<window title="Cell Selection Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-cell', 'treechildren-cell', 'cell', '', 'cell selection tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-cell" rows="4" seltype="cell"> + <treecols> + <treecol id="name" label="Name" primary="true" + sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" flex="2"/> + <treecol id="planet" label="Planet" flex="1"/> + <treecol id="gender" label="Gender" flex="1" cycler="true"/> + </treecols> + <treechildren id="treechildren-cell"> + <treeitem> + <treerow properties="firstrow"> + <treecell label="Mary" value="mary" properties="firstname"/> + <treecell label="206 Garden Avenue" value="206ga"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell/> + <treecell value="19ms"/> + <treecell label="Earth"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem container="true"> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + <treecell label="Saturn"/> + <treecell label="Female" value="f"/> + </treerow> + <treechildren> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + <treecell label="Female" value="f"/> + <treecell label="Neptune"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + <treecell label="Omicron Persei 8"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell label="Neptune"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + </treechildren> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue" selectable="false"/> + <treecell label=""/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + <treecell label="Neptune"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + <treecell label="Earth"/> + <treecell label="Female" value="f"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + <treecell label="Mars"/> + <treecell label="Male" value="m"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree_single.xul b/toolkit/content/tests/chrome/test_tree_single.xul new file mode 100644 index 0000000000..9b64cb488a --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_single.xul @@ -0,0 +1,110 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for single selection tree + --> +<window title="Single Selection Tree" width="500" height="600" + onload="setTimeout(testtag_tree, 0, 'tree-single', 'treechildren-single', + 'single', 'simple', 'single selection tree');" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<tree id="tree-single" rows="4" seltype="single"> + <treecols> + <treecol id="name" label="Name" sort="label" properties="one two" flex="1"/> + <treecol id="address" label="Address" flex="1"/> + </treecols> + <treechildren id="treechildren-single"> + <treeitem> + <treerow properties="firstrow"> + <treecell label="Mary" value="mary" properties="firstname"/> + <treecell label="206 Garden Avenue" value="206ga"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell/> + <treecell value="19ms"/> + </treerow> + </treeitem> + <treeitem container="true"> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + </treerow> + <treechildren> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue" editable="false"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + </treechildren> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Mary"/> + <treecell label="206 Garden Avenue" selectable="false"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Chris"/> + <treecell label="19 Marion Street"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="Sarah"/> + <treecell label="702 Fern Avenue"/> + </treerow> + </treeitem> + <treeitem> + <treerow> + <treecell label="John"/> + <treecell label="99 Westminster Avenue"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/test_tree_view.xul b/toolkit/content/tests/chrome/test_tree_view.xul new file mode 100644 index 0000000000..235e3a5948 --- /dev/null +++ b/toolkit/content/tests/chrome/test_tree_view.xul @@ -0,0 +1,118 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for tree using a custom nsITreeView + --> +<window title="Tree" onload="init()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + +<script src="tree_shared.js"/> + +<script> +<![CDATA[ + +// This is our custom view, based on the treeview interface +var view = +{ + treeData: [["Mary", "206 Garden Avenue"], + ["Chris", "19 Marion Street"], + ["Sarah", "702 Fern Avenue"], + ["John", "99 Westminster Avenue"]], + value: "", + rowCount: 8, + getCellText: function(row, column) { return this.treeData[row % 4][column.index]; }, + getCellValue: function(row, column) { return this.value; }, + setCellText: function(row, column, val) { this.treeData[row % 4][column.index] = val; }, + setCellValue: function(row, column, val) { this.value = val; }, + setTree: function(tree) { this.tree = tree; }, + isContainer: function(row) { return false; }, + isContainerOpen: function(row) { return false; }, + isContainerEmpty: function(row) { return false; }, + isSeparator: function(row) { return false; }, + isSorted: function(row) { return false; }, + isSelectable: function(row, column) { return true; }, + isEditable: function(row, column) { return row != 2 || column.index != 1; }, + getProgressMode: function(row, column) { return Components.interfaces.nsITreeView.PROGRESS_NORMAL; }, + getParentIndex: function(row, column) { return -1; }, + getLevel: function(row) { return 0; }, + hasNextSibling: function(row, column) { return row != this.rowCount - 1; }, + getImageSrc: function(row, column) { return ""; }, + cycleHeader: function(column) { }, + getRowProperties: function(row) { return ""; }, + getCellProperties: function(row, column) { return ""; }, + getColumnProperties: function(column) + { + if (!column.index) { + return "one two"; + } + + return ""; + } +} + +function getCustomTreeViewCellInfo() +{ + var obj = { rows: [] }; + + for (var row = 0; row < view.rowCount; row++) { + var cellInfo = [ ]; + for (var column = 0; column < 1; column++) { + cellInfo.push({ label: "" + view.treeData[row % 4][column], + value: "", + properties: "", + editable: row != 2 || column.index != 1, + selectable: true, + image: "", + mode: Components.interfaces.nsITreeView.PROGRESS_NORMAL }); + } + + obj.rows.push({ cells: cellInfo, + properties: "", + container: false, + separator: false, + children: null, + level: 0, + parent: -1 }); + } + + return obj; +} + +function init() +{ + var tree = document.getElementById("tree-view"); + tree.view = view; + tree.treeBoxObject.ensureRowIsVisible(0); + is(tree.treeBoxObject.getFirstVisibleRow(), 0, "first visible after ensureRowIsVisible on load"); + tree.setAttribute("rows", "4"); + + setTimeout(testtag_tree, 0, "tree-view", "treechildren-view", "multiple", "simple", "tree view"); +} + +]]> +</script> + +<tree id="tree-view"> + <treecols> + <treecol id="name" label="Name" sort="label" flex="1"/> + <treecol id="address" label="Address" flex="1"/> + </treecols> + <treechildren id="treechildren-view"/> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> + diff --git a/toolkit/content/tests/chrome/window_browser_drop.xul b/toolkit/content/tests/chrome/window_browser_drop.xul new file mode 100644 index 0000000000..8a22ccce90 --- /dev/null +++ b/toolkit/content/tests/chrome/window_browser_drop.xul @@ -0,0 +1,254 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Browser Drop Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +<![CDATA[ + +Components.utils.import("resource://testing-common/ContentTask.jsm"); + +function dropOnRemoteBrowserAsync(browser, data, shouldExpectStateChange) { + ContentTask.setTestScope(window); // Need this so is/isnot/ok are available inside the contenttask + return ContentTask.spawn(browser, {data, shouldExpectStateChange}, function*({data, shouldExpectStateChange}) { + let { interfaces: Ci, utils: Cu } = Components; + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + + if (!content.document.documentElement) { + // Wait until the testing document gets loaded. + yield new Promise(resolve => { + let onload = function() { + content.window.removeEventListener("load", onload); + resolve(); + }; + content.window.addEventListener("load", onload); + }); + } + + let dataTransfer = new content.DataTransfer("dragstart", false); + for (let i = 0; i < data.length; i++) { + let types = data[i]; + for (let j = 0; j < types.length; j++) { + dataTransfer.mozSetDataAt(types[j].type, types[j].data, i); + } + } + let event = content.document.createEvent("DragEvent"); + event.initDragEvent("drop", true, true, content, 0, 0, 0, 0, 0, + false, false, false, false, 0, null, dataTransfer); + content.document.body.dispatchEvent(event); + + let links = []; + try { + links = Services.droppedLinkHandler.dropLinks(event, true); + } catch (ex) { + if (shouldExpectStateChange) { + ok(false, "Should not have gotten an exception from the dropped link handler, but got: " + ex); + Cu.reportError(ex); + } + } + + return links; + }); +} + +function* expectLink(browser, expectedLinks, data, testid, onbody=false) { + let lastLinks = []; + let lastLinksPromise = new Promise(resolve => { + browser.droppedLinkHandler = function(event, links) { + info(`droppedLinkHandler called, received links ${JSON.stringify(links)}`); + if (expectedLinks.length == 0) { + ok(false, `droppedLinkHandler called for ${JSON.stringify(links)} which we didn't expect.`); + } + lastLinks = links; + resolve(links); + }; + }); + + function dropOnBrowserSync() { + let dropEl = onbody ? browser.contentDocument.body : browser; + synthesizeDrop(dropEl, dropEl, data, "", dropEl.ownerDocument.defaultView); + } + let links; + if (browser.isRemoteBrowser) { + let remoteLinks = yield dropOnRemoteBrowserAsync(browser, data, expectedLinks.length != 0); + is(remoteLinks.length, expectedLinks.length, testid + " remote links length"); + for (let i = 0, length = remoteLinks.length; i < length; i++) { + is(remoteLinks[i].url, expectedLinks[i].url, testid + "[" + i + "] remote link"); + is(remoteLinks[i].name, expectedLinks[i].name, testid + "[" + i + "] remote name"); + } + + if (expectedLinks.length == 0) { + // There is no way to check if nothing happens asynchronously. + return; + } + + links = yield lastLinksPromise; + } else { + dropOnBrowserSync(); + links = lastLinks; + } + + is(links.length, expectedLinks.length, testid + " links length"); + for (let i = 0, length = links.length; i < length; i++) { + is(links[i].url, expectedLinks[i].url, testid + "[" + i + "] link"); + is(links[i].name, expectedLinks[i].name, testid + "[" + i + "] name"); + } +}; + +function* dropLinksOnBrowser(browser, type) { + // Dropping single text/plain item with single link should open single + // page. + yield* expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" } ], + [ [ { type: "text/plain", + data: "http://www.mozilla.org/" } ] ], + "text/plain drop on browser " + type); + + // Dropping single text/plain item with multiple links should open + // multiple pages. + yield* expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" }, + { url: "http://www.example.com/", + name: "http://www.example.com/" } ], + [ [ { type: "text/plain", + data: "http://www.mozilla.org/\nhttp://www.example.com/" } ] ], + "text/plain with 2 URLs drop on browser " + type); + + // Dropping sinlge unsupported type item should not open anything. + yield* expectLink(browser, + [], + [ [ { type: "text/link", + data: "http://www.mozilla.org/" } ] ], + "text/link drop on browser " + type); + + // Dropping single text/uri-list item with single link should open single + // page. + yield* expectLink(browser, + [ { url: "http://www.example.com/", + name: "http://www.example.com/" } ], + [ [ { type: "text/uri-list", + data: "http://www.example.com/" } ] ], + "text/uri-list drop on browser " + type); + + // Dropping single text/uri-list item with multiple links should open + // multiple pages. + yield* expectLink(browser, + [ { url: "http://www.example.com/", + name: "http://www.example.com/" }, + { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" }], + [ [ { type: "text/uri-list", + data: "http://www.example.com/\nhttp://www.mozilla.org/" } ] ], + "text/uri-list with 2 URLs drop on browser " + type); + + // Name in text/x-moz-url should be handled. + yield* expectLink(browser, + [ { url: "http://www.example.com/", + name: "Example.com" } ], + [ [ { type: "text/x-moz-url", + data: "http://www.example.com/\nExample.com" } ] ], + "text/x-moz-url drop on browser " + type); + + yield* expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "Mozilla.org" }, + { url: "http://www.example.com/", + name: "Example.com" } ], + [ [ { type: "text/x-moz-url", + data: "http://www.mozilla.org/\nMozilla.org\nhttp://www.example.com/\nExample.com" } ] ], + "text/x-moz-url with 2 URLs drop on browser " + type); + + // Dropping multiple items should open multiple pages. + yield* expectLink(browser, + [ { url: "http://www.example.com/", + name: "Example.com" }, + { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" }], + [ [ { type: "text/x-moz-url", + data: "http://www.example.com/\nExample.com" } ], + [ { type: "text/plain", + data: "http://www.mozilla.org/" } ] ], + "text/x-moz-url and text/plain drop on browser " + type); + + // Dropping single item with multiple types should open single page. + yield* expectLink(browser, + [ { url: "http://www.example.org/", + name: "Example.com" } ], + [ [ { type: "text/plain", + data: "http://www.mozilla.org/" }, + { type: "text/x-moz-url", + data: "http://www.example.org/\nExample.com" } ] ], + "text/plain and text/x-moz-url drop on browser " + type); + + // Dropping javascript or data: URLs should fail: + yield* expectLink(browser, + [], + [ [ { type: "text/plain", + data: "javascript:'bad'" } ] ], + "text/plain javascript url drop on browser " + type); + yield* expectLink(browser, + [], + [ [ { type: "text/plain", + data: "jAvascript:'also bad'" } ] ], + "text/plain mixed-case javascript url drop on browser " + type); + yield* expectLink(browser, + [], + [ [ { type: "text/plain", + data: "data:text/html,bad" } ] ], + "text/plain data url drop on browser " + type); + + // Dropping a chrome url should fail as we don't have a source node set, + // defaulting to a source of file:/// + yield* expectLink(browser, + [], + [ [ { type: "text/x-moz-url", + data: "chrome://browser/content/browser.xul" } ] ], + "text/x-moz-url chrome url drop on browser " + type); + + if (browser.type == "content") { + yield ContentTask.spawn(browser, null, function() { + content.window.stopMode = true; + }); + + // stopPropagation should not prevent the browser link handling from occuring + yield* expectLink(browser, + [ { url: "http://www.mozilla.org/", + name: "http://www.mozilla.org/" } ], + [ [ { type: "text/uri-list", + data: "http://www.mozilla.org/" } ] ], + "text/x-moz-url drop on browser with stopPropagation drop event", true); + + yield ContentTask.spawn(browser, null, function() { + content.window.cancelMode = true; + }); + + // Canceling the event, however, should prevent the link from being handled. + yield* expectLink(browser, + [], + [ [ { type: "text/uri-list", data: "http://www.mozilla.org/" } ] ], + "text/x-moz-url drop on browser with cancelled drop event", true); + } +} + +function info(msg) { window.opener.wrappedJSObject.SimpleTest.info(msg); } +function is(l, r, n) { window.opener.wrappedJSObject.SimpleTest.is(l,r,n); } +function ok(v, n) { window.opener.wrappedJSObject.SimpleTest.ok(v,n); } + +]]> +</script> + +<browser id="chromechild" src="about:blank"/> +<browser id="contentchild" type="content" width="100" height="100" + src="data:text/html,<html draggable='true'><body draggable='true' style='width: 100px; height: 100px;' ondragover='event.preventDefault()' ondrop='if (window.stopMode) event.stopPropagation(); if (window.cancelMode) event.preventDefault();'></body></html>"/> + +<browser id="remote-contentchild" type="content" width="100" height="100" remote="true" + src="data:text/html,<html draggable='true'><body draggable='true' style='width: 100px; height: 100px;' ondragover='event.preventDefault()' ondrop='if (window.stopMode) event.stopPropagation(); if (window.cancelMode) event.preventDefault();'></body></html>"/> +</window> diff --git a/toolkit/content/tests/chrome/window_chromemargin.xul b/toolkit/content/tests/chrome/window_chromemargin.xul new file mode 100644 index 0000000000..3dec6d1379 --- /dev/null +++ b/toolkit/content/tests/chrome/window_chromemargin.xul @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="window" title="Subframe Origin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> +chrome margins rock! +<script> + +// Tests parsing of the chrome margin attrib on a window. + +function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); +} + +function doSingleTest(param, shouldSucceed) +{ + var exception = null; + try { + document.documentElement.removeAttribute("chromemargin"); + document.documentElement.setAttribute("chromemargin", param); + ok(document. + documentElement. + getAttribute("chromemargin") == param, "couldn't set/get chromemargin?"); + } catch (ex) { + exception = ex; + } + if (shouldSucceed) + ok(!exception, "failed for param:'" + param + "'"); + else + ok(exception, "did not fail for invalid param:'" + param + "'"); + return true; +} + +function runTests() +{ + var doc = document.documentElement; + + // make sure we can set and get + doc.setAttribute("chromemargin", "0,0,0,0"); + ok(doc.getAttribute("chromemargin") == "0,0,0,0", "couldn't set/get chromemargin?"); + doc.setAttribute("chromemargin", "-1,-1,-1,-1"); + ok(doc.getAttribute("chromemargin") == "-1,-1,-1,-1", "couldn't set/get chromemargin?"); + + // test remove + doc.removeAttribute("chromemargin"); + ok(doc.getAttribute("chromemargin") == "", "couldn't remove chromemargin?"); + + // we already test these really well in a c++ test in widget + doSingleTest("1,2,3,4", true); + doSingleTest("-2,-2,-2,-2", true); + doSingleTest("1,1,1,1", true); + doSingleTest("", false); + doSingleTest("12123123", false); + doSingleTest("0,-1,-1,-1", true); + doSingleTest("-1,0,-1,-1", true); + doSingleTest("-1,-1,0,-1", true); + doSingleTest("-1,-1,-1,0", true); + doSingleTest("1234567890,1234567890,1234567890,1234567890", true); + doSingleTest("-1,-1,-1,-1", true); + + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window); + +</script> +</window> diff --git a/toolkit/content/tests/chrome/window_cursorsnap_dialog.xul b/toolkit/content/tests/chrome/window_cursorsnap_dialog.xul new file mode 100644 index 0000000000..df6f0bf026 --- /dev/null +++ b/toolkit/content/tests/chrome/window_cursorsnap_dialog.xul @@ -0,0 +1,104 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<dialog title="Cursor snapping test" id="dialog" + width="600" height="600" + onload="onload();" + onunload="onunload();" + buttons="accept,cancel" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function ok(aCondition, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage); +} + +function is(aLeft, aRight, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage); +} + +function isnot(aLeft, aRight, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage); +} + +function canRetryTest() +{ + return window.opener.wrappedJSObject.canRetryTest(); +} + +function getTimeoutTime() +{ + return window.opener.wrappedJSObject.getTimeoutTime(); +} + +var gTimer; +var gRetry; + +function finishByTimeout() +{ + var button = document.getElementById("dialog").getButton("accept"); + if (button.disabled) + ok(true, "cursor is NOT snapped to the disabled button (dialog)"); + else if (button.hidden) + ok(true, "cursor is NOT snapped to the hidden button (dialog)"); + else { + if (!canRetryTest()) { + ok(false, "cursor is NOT snapped to the default button (dialog)"); + } else { + // otherwise, this may be unexpected timeout, we should retry the test. + gRetry = true; + } + } + finish(); +} + +function finish() +{ + window.close(); +} + +function onMouseMove(aEvent) +{ + var button = document.getElementById("dialog").getButton("accept"); + if (button.disabled) + ok(false, "cursor IS snapped to the disabled button (dialog)"); + else if (button.hidden) + ok(false, "cursor IS snapped to the hidden button (dialog)"); + else + ok(true, "cursor IS snapped to the default button (dialog)"); + clearTimeout(gTimer); + finish(); +} + +function onload() +{ + var button = document.getElementById("dialog").getButton("accept"); + button.addEventListener("mousemove", onMouseMove, false); + + if (window.opener.wrappedJSObject.gDisable) { + button.disabled = true; + } + if (window.opener.wrappedJSObject.gHidden) { + button.hidden = true; + } + gRetry = false; + gTimer = setTimeout(finishByTimeout, getTimeoutTime()); +} + +function onunload() +{ + if (gRetry) { + window.opener.wrappedJSObject.retryCurrentTest(); + } else { + window.opener.wrappedJSObject.runNextTest(); + } +} + +]]> +</script> + +</dialog> diff --git a/toolkit/content/tests/chrome/window_cursorsnap_wizard.xul b/toolkit/content/tests/chrome/window_cursorsnap_wizard.xul new file mode 100644 index 0000000000..a226d02b74 --- /dev/null +++ b/toolkit/content/tests/chrome/window_cursorsnap_wizard.xul @@ -0,0 +1,111 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<wizard title="Cursor snapping test" id="wizard" + width="600" height="600" + onload="onload();" + onunload="onunload();" + buttons="accept,cancel" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <wizardpage> + <label value="first page"/> + </wizardpage> + + <wizardpage> + <label value="second page"/> + </wizardpage> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function ok(aCondition, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.ok(aCondition, aMessage); +} + +function is(aLeft, aRight, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.is(aLeft, aRight, aMessage); +} + +function isnot(aLeft, aRight, aMessage) +{ + window.opener.wrappedJSObject.SimpleTest.isnot(aLeft, aRight, aMessage); +} + +function canRetryTest() +{ + return window.opener.wrappedJSObject.canRetryTest(); +} + +function getTimeoutTime() +{ + return window.opener.wrappedJSObject.getTimeoutTime(); +} + +var gTimer; +var gRetry = false; + +function finishByTimeout() +{ + var button = document.getElementById("wizard").getButton("next"); + if (button.disabled) + ok(true, "cursor is NOT snapped to the disabled button (wizard)"); + else if (button.hidden) + ok(true, "cursor is NOT snapped to the hidden button (wizard)"); + else { + if (!canRetryTest()) { + ok(false, "cursor is NOT snapped to the default button (wizard)"); + } else { + // otherwise, this may be unexpected timeout, we should retry the test. + gRetry = true; + } + } + finish(); +} + +function finish() +{ + window.close(); +} + +function onMouseMove() +{ + var button = document.getElementById("wizard").getButton("next"); + if (button.disabled) + ok(false, "cursor IS snapped to the disabled button (wizard)"); + else if (button.hidden) + ok(false, "cursor IS snapped to the hidden button (wizard)"); + else + ok(true, "cursor IS snapped to the default button (wizard)"); + clearTimeout(gTimer); + finish(); +} + +function onload() +{ + var button = document.getElementById("wizard").getButton("next"); + button.addEventListener("mousemove", onMouseMove, false); + + if (window.opener.wrappedJSObject.gDisable) { + button.disabled = true; + } + if (window.opener.wrappedJSObject.gHidden) { + button.hidden = true; + } + gTimer = setTimeout(finishByTimeout, getTimeoutTime()); +} + +function onunload() +{ + if (gRetry) { + window.opener.wrappedJSObject.retryCurrentTest(); + } else { + window.opener.wrappedJSObject.runNextTest(); + } +} + +]]> +</script> + +</wizard> diff --git a/toolkit/content/tests/chrome/window_keys.xul b/toolkit/content/tests/chrome/window_keys.xul new file mode 100644 index 0000000000..79de9ac45e --- /dev/null +++ b/toolkit/content/tests/chrome/window_keys.xul @@ -0,0 +1,202 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Key Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var gExpected = null; + +const kIsWin = navigator.platform.indexOf("Win") >= 0; + +// Only on Windows, osKey state is ignored when there is no shortcut key handler +// which exactly matches with osKey state. +var keysToTest = [ + ["k-v", "V", { } ], + ["", "V", { shiftKey: true } ], + ["k-v-scy", "V", { ctrlKey: true } ], + ["", "V", { altKey: true } ], + ["", "V", { metaKey: true } ], + [kIsWin ? "k-v" : "", "V", { osKey: true } ], + ["k-v-scy", "V", { shiftKey: true, ctrlKey: true } ], + ["", "V", { shiftKey: true, ctrlKey: true, altKey: true } ], + ["k-e-y", "E", { } ], + ["", "E", { shiftKey: true } ], + ["", "E", { ctrlKey: true } ], + ["", "E", { altKey: true } ], + ["", "E", { metaKey: true } ], + [kIsWin ? "k-e-y" : "", "E", { osKey: true } ], + ["k-d-a", "D", { altKey: true } ], + ["k-8-m", "8", { metaKey: true } ], + [kIsWin ? "k-8-m" : "", "8", { metaKey: true, osKey: true } ], + ["k-a-o", "A", { osKey: true } ], + ["", "A", { osKey: true, metaKey: true } ], + ["", "B", {} ], + ["k-b-myo", "B", { osKey: true } ], + ["k-b-myo", "B", { osKey: true, metaKey: true } ], + ["k-f-oym", "F", { metaKey: true } ], + ["k-f-oym", "F", { metaKey: true, osKey: true } ], + ["k-c-scaym", "C", { metaKey: true } ], + ["k-c-scaym", "C", { shiftKey: true, ctrlKey: true, altKey: true, metaKey: true } ], + [kIsWin ? "k-c-scaym" : "", "C", { shiftKey: true, ctrlKey: true, altKey: true, metaKey: true, osKey: true } ], + ["", "V", { shiftKey: true, ctrlKey: true, altKey: true } ], + ["k-h-l", "H", { accelKey: true } ], +// ["k-j-s", "J", { accessKey: true } ], + ["", "T", { } ], + ["k-g-c", "G", { ctrlKey: true } ], + ["k-g-co", "G", { ctrlKey: true, osKey: true } ], + ["scommand", "Y", { } ], + ["", "U", { } ], +]; + +function runTest() +{ + iterateKeys(true, "normal"); + + var keyset = document.getElementById("keyset"); + keyset.setAttribute("disabled", "true"); + iterateKeys(false, "disabled"); + + var keyset = document.getElementById("keyset"); + keyset.removeAttribute("disabled"); + iterateKeys(true, "reenabled"); + + keyset.parentNode.removeChild(keyset); + iterateKeys(false, "removed"); + + document.documentElement.appendChild(keyset); + iterateKeys(true, "appended"); + + var accelText = menuitem => menuitem.getAttribute("acceltext").toLowerCase(); + + $("menubutton").open = true; + + // now check if a menu updates its accelerator text when a key attribute is changed + var menuitem1 = $("menuitem1"); + ok(accelText(menuitem1).indexOf("d") >= 0, "menuitem1 accelText before"); + if (kIsWin) { + ok(accelText(menuitem1).indexOf("alt") >= 0, "menuitem1 accelText modifier before"); + } + + menuitem1.setAttribute("key", "k-s-c"); + ok(accelText(menuitem1).indexOf("s") >= 0, "menuitem1 accelText after"); + if (kIsWin) { + ok(accelText(menuitem1).indexOf("ctrl") >= 0, "menuitem1 accelText modifier after"); + } + + menuitem1.setAttribute("acceltext", "custom"); + is(accelText(menuitem1), "custom", "menuitem1 accelText set custom"); + menuitem1.removeAttribute("acceltext"); + ok(accelText(menuitem1).indexOf("s") >= 0, "menuitem1 accelText remove"); + if (kIsWin) { + ok(accelText(menuitem1).indexOf("ctrl") >= 0, "menuitem1 accelText modifier remove"); + } + + var menuitem2 = $("menuitem2"); + is(accelText(menuitem2), "", "menuitem2 accelText before"); + menuitem2.setAttribute("key", "k-s-c"); + ok(accelText(menuitem2).indexOf("s") >= 0, "menuitem2 accelText before"); + if (kIsWin) { + ok(accelText(menuitem2).indexOf("ctrl") >= 0, "menuitem2 accelText modifier before"); + } + + menuitem2.setAttribute("key", "k-h-l"); + ok(accelText(menuitem2).indexOf("h") >= 0, "menuitem2 accelText after"); + if (kIsWin) { + ok(accelText(menuitem2).indexOf("ctrl") >= 0, "menuitem2 accelText modifier after"); + } + + menuitem2.removeAttribute("key"); + is(accelText(menuitem2), "", "menuitem2 accelText after remove"); + + $("menubutton").open = false; + + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); +} + +function iterateKeys(enabled, testid) +{ + for (var k = 0; k < keysToTest.length; k++) { + gExpected = keysToTest[k]; + var expectedKey = gExpected[0]; + if (!gExpected[2].accessKey || navigator.platform.indexOf("Mac") == -1) { + synthesizeKey(gExpected[1], gExpected[2]); + ok((enabled && expectedKey) || expectedKey == "k-d-a" ? + !gExpected : gExpected, testid + " key step " + (k + 1)); + } + } +} + +function checkKey(event) +{ + // the first element of the gExpected array holds the id of the <key> element + // that was expected. If this is empty, a handler wasn't expected to be called + if (gExpected[0]) + is(event.originalTarget.id, gExpected[0], "key " + gExpected[1]); + else + is("key " + event.originalTarget.id + " was activated", "", "key " + gExpected[1]); + gExpected = null; +} + +function is(l, r, n) { window.opener.wrappedJSObject.SimpleTest.is(l,r,n); } +function ok(v, n) { window.opener.wrappedJSObject.SimpleTest.ok(v,n); } + +SimpleTest.waitForFocus(runTest); + +]]> +</script> + +<command id="scommand" oncommand="checkKey(event)"/> +<command id="scommand-disabled" disabled="true"/> + +<keyset id="keyset"> + <key id="k-v" key="v" oncommand="checkKey(event)"/> + <key id="k-v-scy" key="v" modifiers="shift any control" oncommand="checkKey(event)"/> + <key id="k-e-y" key="e" modifiers="any" oncommand="checkKey(event)"/> + <key id="k-8-m" key="8" modifiers="meta" oncommand="checkKey(event)"/> + <key id="k-a-o" key="a" modifiers="os" oncommand="checkKey(event)"/> + <key id="k-b-myo" key="b" modifiers="meta any os" oncommand="checkKey(event)"/> + <key id="k-f-oym" key="f" modifiers="os any meta" oncommand="checkKey(event)"/> + <key id="k-c-scaym" key="c" modifiers="shift control alt any meta" oncommand="checkKey(event)"/> + <key id="k-h-l" key="h" modifiers="accel" oncommand="checkKey(event)"/> + <key id="k-j-s" key="j" modifiers="access" oncommand="checkKey(event)"/> + <key id="k-t-y" disabled="true" key="t" oncommand="checkKey(event)"/> + <key id="k-g-c" key="g" modifiers="control" oncommand="checkKey(event)"/> + <key id="k-g-co" key="g" modifiers="control os" oncommand="checkKey(event)"/> + <key id="k-y" key="y" command="scommand"/> + <key id="k-u" key="u" command="scommand-disabled"/> +</keyset> + +<keyset id="keyset2"> + <key id="k-d-a" key="d" modifiers="alt" oncommand="checkKey(event)"/> + <key id="k-s-c" key="s" modifiers="control" oncommand="checkKey(event)"/> +</keyset> + +<button id="menubutton" label="Menu" type="menu"> + <menupopup> + <menuitem id="menuitem1" label="Item 1" key="k-d-a"/> + <menuitem id="menuitem2" label="Item 2"/> + </menupopup> +</button> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_largemenu.xul b/toolkit/content/tests/chrome/window_largemenu.xul new file mode 100644 index 0000000000..72e1c077d8 --- /dev/null +++ b/toolkit/content/tests/chrome/window_largemenu.xul @@ -0,0 +1,425 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Large Menu Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<!-- + This test checks that a large menu is displayed with arrow buttons + and is on the screen. + --> + +<script> +<![CDATA[ + +var gOverflowed = false, gUnderflowed = false; +var gContextMenuTests = false; +var gScreenY = -1; +var gTestIndex = 0; +var gTests = ["open normal", "open when bottom would overlap", "open with scrolling", + "open after scrolling", "open small again", + "menu movement", "panel movement", + "context menu enough space below", + "context menu more space above", + "context menu too big either side", + "context menu larger than screen", + "context menu flips horizontally on osx"]; +function getScreenXY(element) +{ + var screenX, screenY; + var mouseFn = function(event) { + screenX = event.screenX - 1; + screenY = event.screenY - 1; + } + + // a hacky way to get the screen position of an element without using the box object + window.addEventListener("mousedown", mouseFn, false); + synthesizeMouse(element, 1, 1, { }); + window.removeEventListener("mousedown", mouseFn, false); + + return [screenX, screenY]; +} + +function hidePopup() { + window.requestAnimationFrame( + function() { + setTimeout( + function() { + document.getElementById("popup").hidePopup(); + }, 0); + }); +} + +function runTests() +{ + [, gScreenY] = getScreenXY(document.documentElement); + nextTest(); +} + +function nextTest() +{ + gOverflowed = false, gUnderflowed = false; + + var y = screen.height; + if (gTestIndex == 1) // open with bottom overlap test: + y -= 100; + else + y /= 2; + + var popup = document.getElementById("popup"); + if (gTestIndex == 2) { + // add some more menuitems so that scrolling will be necessary + var moreItemCount = Math.round(screen.height / popup.firstChild.getBoundingClientRect().height); + for (var t = 1; t <= moreItemCount; t++) { + var menu = document.createElement("menuitem"); + menu.setAttribute("label", "More" + t); + popup.appendChild(menu); + } + } + else if (gTestIndex == 4) { + // remove the items added in test 2 above + while (popup.childNodes.length > 15) + popup.removeChild(popup.lastChild); + } + + window.requestAnimationFrame(function() { + setTimeout( + function() { + popup.openPopupAtScreen(100, y, false); + }, 0); + }); +} + +function popupShown() +{ + if (gTests[gTestIndex] == "menu movement") + return testPopupMovement(); + + if (gContextMenuTests) + return contextMenuPopupShown(); + + var popup = document.getElementById("popup"); + var rect = popup.getBoundingClientRect(); + var sbo = document.getAnonymousNodes(popup)[0].scrollBoxObject; + var expectedScrollPos = 0; + + if (gTestIndex == 0) { + // the popup should be in the center of the screen + // note that if the height is odd, the y-offset will have been rounded + // down when we pass the fractional value to openPopupAtScreen above. + is(Math.round(rect.top) + gScreenY, Math.floor(screen.height / 2), + gTests[gTestIndex] + " top"); + ok(Math.round(rect.bottom) + gScreenY < screen.height, + gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow") + } + else if (gTestIndex == 1) { + // the popup was supposed to open 100 pixels from the bottom, but that + // would put it off screen so ... + if (platformIsMac()) { + // On OSX the popup is constrained so it remains within the + // bounds of the screen + ok(Math.round(rect.top) + gScreenY >= screen.top, gTests[gTestIndex] + " top"); + is(Math.round(rect.bottom) + gScreenY, screen.availTop + screen.availHeight, gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow"); + } + else { + // On other platforms the menu should be flipped to have its bottom + // edge 100 pixels from the bottom + ok(Math.round(rect.top) + gScreenY >= screen.top, gTests[gTestIndex] + " top"); + is(Math.round(rect.bottom) + gScreenY, screen.height - 100, + gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow"); + } + } + else if (gTestIndex == 2) { + // the popup is too large so ensure that it is on screen + ok(Math.round(rect.top) + gScreenY >= screen.top, gTests[gTestIndex] + " top"); + ok(Math.round(rect.bottom) + gScreenY <= screen.height, gTests[gTestIndex] + " bottom"); + ok(gOverflowed && !gUnderflowed, gTests[gTestIndex] + " overflow") + + sbo.scrollTo(0, 40); + expectedScrollPos = 40; + } + else if (gTestIndex == 3) { + expectedScrollPos = 40; + } + else if (gTestIndex == 4) { + // note that if the height is odd, the y-offset will have been rounded + // down when we pass the fractional value to openPopupAtScreen above. + is(Math.round(rect.top) + gScreenY, Math.floor(screen.height / 2), + gTests[gTestIndex] + " top"); + ok(Math.round(rect.bottom) + gScreenY < screen.height, + gTests[gTestIndex] + " bottom"); + ok(!gOverflowed && gUnderflowed, gTests[gTestIndex] + " overflow"); + } + + is(sbo.positionY, expectedScrollPos, "menu scroll position"); + + hidePopup(); +} + +function is(l, r, n) { window.opener.wrappedJSObject.SimpleTest.is(l,r,n); } +function ok(v, n) { window.opener.wrappedJSObject.SimpleTest.ok(v,n); } + +var oldx, oldy, waitSteps = 0; +function moveWindowTo(x, y, callback, arg) +{ + if (!waitSteps) { + oldx = window.screenX; + oldy = window.screenY; + window.moveTo(x, y); + + waitSteps++; + setTimeout(moveWindowTo, 100, x, y, callback, arg); + return; + } + + if (window.screenX == oldx && window.screenY == oldy) { + if (waitSteps++ > 10) { + ok(false, "Window never moved properly to " + x + "," + y); + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); + } + + setTimeout(moveWindowTo, 100, x, y, callback, arg); + } + else { + waitSteps = 0; + callback(arg); + } +} + +function popupHidden() +{ + gTestIndex++; + if (gTestIndex == gTests.length) { + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); + } + else if (gTests[gTestIndex] == "context menu enough space below") { + gContextMenuTests = true; + moveWindowTo(window.screenX, screen.availTop + 10, + () => synthesizeMouse(document.getElementById("label"), 4, 4, { type: "contextmenu", button: 2 })); + } + else if (gTests[gTestIndex] == "menu movement") { + document.getElementById("popup").openPopup( + document.getElementById("label"), "after_start", 0, 0, false, false); + } + else if (gTests[gTestIndex] == "panel movement") { + document.getElementById("panel").openPopup( + document.getElementById("label"), "after_start", 0, 0, false, false); + } + else if (gContextMenuTests) { + contextMenuPopupHidden(); + } + else { + nextTest(); + } +} + +function contextMenuPopupShown() +{ + var popup = document.getElementById("popup"); + var rect = popup.getBoundingClientRect(); + var labelrect = document.getElementById("label").getBoundingClientRect(); + + // Click to open popup in popupHidden() occurs at (4,4) in label's coordinate space + var clickX = clickY = 4; + + var testPopupAppearedRightOfCursor = true; + switch (gTests[gTestIndex]) { + case "context menu enough space below": + is(rect.top, labelrect.top + clickY + (platformIsMac() ? -6 : 2), gTests[gTestIndex] + " top"); + break; + case "context menu more space above": + if (platformIsMac()) { + let screenY; + [, screenY] = getScreenXY(popup); + // Macs constrain their popup menus vertically rather than flip them. + is(screenY, screen.availTop + screen.availHeight - rect.height, gTests[gTestIndex] + " top"); + } else { + is(rect.top, labelrect.top + clickY - rect.height - 2, gTests[gTestIndex] + " top"); + } + + break; + case "context menu too big either side": + [, gScreenY] = getScreenXY(document.documentElement); + // compare against the available size as well as the total size, as some + // platforms allow the menu to overlap os chrome and others do not + var pos = (screen.availTop + screen.availHeight - rect.height) - gScreenY; + var availPos = (screen.top + screen.height - rect.height) - gScreenY; + ok(rect.top == pos || rect.top == availPos, + gTests[gTestIndex] + " top"); + break; + case "context menu larger than screen": + ok(rect.top == -(gScreenY - screen.availTop) || rect.top == -(gScreenY - screen.top), gTests[gTestIndex] + " top"); + break; + case "context menu flips horizontally on osx": + testPopupAppearedRightOfCursor = false; + if (platformIsMac()) { + is(Math.round(rect.right), labelrect.left + clickX - 1, gTests[gTestIndex] + " right"); + } + break; + } + + if (testPopupAppearedRightOfCursor) { + is(rect.left, labelrect.left + clickX + (platformIsMac() ? 1 : 2), gTests[gTestIndex] + " left"); + } + + hidePopup(); +} + +function contextMenuPopupHidden() +{ + var screenAvailBottom = screen.availTop + screen.availHeight; + + if (gTests[gTestIndex] == "context menu more space above") { + moveWindowTo(window.screenX, screenAvailBottom - 80, nextContextMenuTest, -1); + } + else if (gTests[gTestIndex] == "context menu too big either side") { + moveWindowTo(window.screenX, screenAvailBottom / 2 - 80, nextContextMenuTest, screenAvailBottom / 2 + 120); + } + else if (gTests[gTestIndex] == "context menu larger than screen") { + nextContextMenuTest(screen.availHeight + 80); + } + else if (gTests[gTestIndex] == "context menu flips horizontally on osx") { + var popup = document.getElementById("popup"); + var popupWidth = popup.getBoundingClientRect().width; + moveWindowTo(screen.availLeft + screen.availWidth - popupWidth, 100, nextContextMenuTest, -1); + } +} + +function nextContextMenuTest(desiredHeight) +{ + if (desiredHeight >= 0) { + var popup = document.getElementById("popup"); + var height = popup.getBoundingClientRect().height; + var itemheight = document.getElementById("firstitem").getBoundingClientRect().height; + while (height < desiredHeight) { + var menu = document.createElement("menuitem"); + menu.setAttribute("label", "Item"); + popup.appendChild(menu); + height += itemheight; + } + } + + synthesizeMouse(document.getElementById("label"), 4, 4, { type: "contextmenu", button: 2 }); +} + +function testPopupMovement() +{ + var button = document.getElementById("label"); + var isPanelTest = (gTests[gTestIndex] == "panel movement"); + var popup = document.getElementById(isPanelTest ? "panel" : "popup"); + + var screenX, screenY, buttonScreenX, buttonScreenY; + var rect = popup.getBoundingClientRect(); + + var overlapOSChrome = !platformIsMac(); + popup.moveTo(1, 1); + [screenX, screenY] = getScreenXY(popup); + + var expectedx = 1, expectedy = 1; + if (!isPanelTest && !overlapOSChrome) { + if (screen.availLeft >= 1) expectedx = screen.availLeft; + if (screen.availTop >= 1) expectedy = screen.availTop; + } + is(screenX, expectedx, gTests[gTestIndex] + " (1, 1) x"); + is(screenY, expectedy, gTests[gTestIndex] + " (1, 1) y"); + + popup.moveTo(100, 8000); + if (isPanelTest) { + expectedy = 8000; + } + else { + expectedy = (overlapOSChrome ? screen.height + screen.top : screen.availHeight + screen.availTop) - + Math.round(rect.height); + } + + [screenX, screenY] = getScreenXY(popup); + is(screenX, 100, gTests[gTestIndex] + " (100, 8000) x"); + is(screenY, expectedy, gTests[gTestIndex] + " (100, 8000) y"); + + popup.moveTo(6000, 100); + + if (isPanelTest) { + expectedx = 6000; + } + else { + expectedx = (overlapOSChrome ? screen.width + screen.left : screen.availWidth + screen.availLeft) - + Math.round(rect.width); + } + + [screenX, screenY] = getScreenXY(popup); + is(screenX, expectedx, gTests[gTestIndex] + " (6000, 100) x"); + is(screenY, 100, gTests[gTestIndex] + " (6000, 100) y"); + + is(popup.left, "", gTests[gTestIndex] + " left is empty after moving"); + is(popup.top, "", gTests[gTestIndex] + " top is empty after moving"); + popup.setAttribute("left", "80"); + popup.setAttribute("top", "82"); + [screenX, screenY] = getScreenXY(popup); + is(screenX, 80, gTests[gTestIndex] + " set left and top x"); + is(screenY, 82, gTests[gTestIndex] + " set left and top y"); + popup.moveTo(95, 98); + [screenX, screenY] = getScreenXY(popup); + is(screenX, 95, gTests[gTestIndex] + " move after set left and top x"); + is(screenY, 98, gTests[gTestIndex] + " move after set left and top y"); + is(popup.left, "95", gTests[gTestIndex] + " left is set after moving"); + is(popup.top, "98", gTests[gTestIndex] + " top is set after moving"); + popup.removeAttribute("left"); + popup.removeAttribute("top"); + + popup.moveTo(-1, -1); + [screenX, screenY] = getScreenXY(popup); + + expectedx = (overlapOSChrome ? screen.left : screen.availLeft); + expectedy = (overlapOSChrome ? screen.top : screen.availTop); + + is(screenX, expectedx, gTests[gTestIndex] + " move after set left and top x to -1"); + is(screenY, expectedy, gTests[gTestIndex] + " move after set left and top y to -1"); + is(popup.left, "", gTests[gTestIndex] + " left is not set after moving to -1"); + is(popup.top, "", gTests[gTestIndex] + " top is not set after moving to -1"); + + popup.hidePopup(); +} + +function platformIsMac() +{ + return navigator.platform.indexOf("Mac") > -1; +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window); + +]]> +</script> + +<button id="label" label="OK" context="popup"/> +<menupopup id="popup" onpopupshown="popupShown();" onpopuphidden="popupHidden();" + onoverflow="gOverflowed = true" onunderflow="gUnderflowed = true;"> + <menuitem id="firstitem" label="1"/> + <menuitem label="2"/> + <menuitem label="3"/> + <menuitem label="4"/> + <menuitem label="5"/> + <menuitem label="6"/> + <menuitem label="7"/> + <menuitem label="8"/> + <menuitem label="9"/> + <menuitem label="10"/> + <menuitem label="11"/> + <menuitem label="12"/> + <menuitem label="13"/> + <menuitem label="14"/> + <menuitem label="15"/> +</menupopup> + +<panel id="panel" onpopupshown="testPopupMovement();" onpopuphidden="popupHidden();" style="margin: 0"> + <button label="OK"/> +</panel> + +</window> diff --git a/toolkit/content/tests/chrome/window_panel.xul b/toolkit/content/tests/chrome/window_panel.xul new file mode 100644 index 0000000000..b99b52dfaf --- /dev/null +++ b/toolkit/content/tests/chrome/window_panel.xul @@ -0,0 +1,312 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- + XUL Widget Test for panels + --> +<window title="Titlebar" width="200" height="200" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<tree id="tree" seltype="single" width="100" height="100"> + <treecols> + <treecol flex="1"/> + <treecol flex="1"/> + </treecols> + <treechildren id="treechildren"> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem> + </treechildren> +</tree> + + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var currentTest = null; + +function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + window.opener.wrappedJSObject.SimpleTest.is(left, right, message); +} + +function test_panels() +{ + checkTreeCoords(); + + addEventListener("popupshowing", popupShowing, false); + addEventListener("popupshown", popupShown, false); + addEventListener("popuphidden", nextTest, false); + nextTest(); +} + +function nextTest() +{ + if (!tests.length) { + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + return; + } + + currentTest = tests.shift(); + var panel = createPanel(currentTest.attrs); + currentTest.test(panel); +} + +function popupShowing(event) +{ + var rect = event.target.getOuterScreenRect(); + ok(!rect.left && !rect.top && !rect.width && !rect.height, + currentTest.testname + " empty rectangle during popupshowing"); +} + +var waitSteps = 0; +function popupShown(event) +{ + var panel = event.target; + + if (waitSteps > 0 && navigator.platform.indexOf("Linux") >= 0 && + panel.boxObject.screenY == 210) { + waitSteps--; + setTimeout(popupShown, 10, event); + return; + } + + currentTest.result(currentTest.testname + " ", panel); + panel.hidePopup(); +} + +function createPanel(attrs) +{ + var panel = document.createElement("panel"); + for (var a in attrs) { + panel.setAttribute(a, attrs[a]); + } + + var button = document.createElement("button"); + panel.appendChild(button); + button.label = "OK"; + button.width = 120; + button.height = 40; + button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;"); + panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;"); + return document.documentElement.appendChild(panel); +} + +function checkTreeCoords() +{ + var tree = $("tree"); + var treechildren = $("treechildren"); + tree.currentIndex = 0; + tree.treeBoxObject.scrollToRow(0); + synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { }); + is(tree.currentIndex, 1, "tree selection"); + + tree.treeBoxObject.scrollToRow(2); + synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { }); + is(tree.currentIndex, 3, "tree selection after scroll"); +} + +var tests = [ + { + testname: "normal panel", + attrs: { }, + test: function(panel) { + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 0, this.testname + " screen left before open"); + is(screenRect.top, 0, this.testname + " screen top before open"); + is(screenRect.width, 0, this.testname + " screen width before open"); + is(screenRect.height, 0, this.testname + " screen height before open"); + + panel.openPopupAtScreen(200, 210); + }, + result: function(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, 200 - mozInnerScreenX, testname + "left"); + is(panelrect.top, 210 - mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 200, testname + " screen left"); + is(screenRect.top, 210, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, + { + // only noautohide panels support titlebars, so one shouldn't be shown here + testname: "autohide panel with titlebar", + attrs: { titlebar: "normal" }, + test: function(panel) { + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 0, this.testname + " screen left before open"); + is(screenRect.top, 0, this.testname + " screen top before open"); + is(screenRect.width, 0, this.testname + " screen width before open"); + is(screenRect.height, 0, this.testname + " screen height before open"); + + panel.openPopupAtScreen(200, 210); + }, + result: function(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, 200 - mozInnerScreenX, testname + "left"); + is(panelrect.top, 210 - mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 200, testname + " screen left"); + is(screenRect.top, 210, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, + { + testname: "noautohide panel with titlebar", + attrs: { noautohide: true, titlebar: "normal" }, + test: function(panel) { + waitSteps = 25; + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, 0, this.testname + " screen left before open"); + is(screenRect.top, 0, this.testname + " screen top before open"); + is(screenRect.width, 0, this.testname + " screen width before open"); + is(screenRect.height, 0, this.testname + " screen height before open"); + + panel.openPopupAtScreen(200, 210); + }, + result: function(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + ok(panelrect.left >= 200 - mozInnerScreenX, testname + "left"); + if (navigator.platform.indexOf("Linux") < 0) { + ok(panelrect.top >= 210 - mozInnerScreenY + 10, testname + "top greater"); + } + ok(panelrect.top <= 210 - mozInnerScreenY + 32, testname + "top less"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + if (navigator.platform.indexOf("Linux") < 0) { + is(screenRect.left, 200, testname + " screen left"); + is(screenRect.top, 210, testname + " screen top"); + } + ok(screenRect.width >= 120 && screenRect.width <= 140, testname + " screen width"); + ok(screenRect.height >= 40 && screenRect.height <= 80, testname + " screen height"); + + var gotMouseEvent = false; + function mouseMoved(event) + { + is(event.clientY, panelrect.top + 10, + "popup clientY"); + is(event.screenY, panel.boxObject.screenY + 10, + "popup screenY"); + is(event.originalTarget, panel.firstChild, "popup target"); + gotMouseEvent = true; + } + + panel.addEventListener("mousemove", mouseMoved, true); + synthesizeMouse(panel, 10, 10, { type: "mousemove" }); + ok(gotMouseEvent, "mouse event on panel"); + panel.removeEventListener("mousemove", mouseMoved, true); + + var tree = $("tree"); + tree.currentIndex = 0; + panel.appendChild(tree); + checkTreeCoords(); + } + }, + { + testname: "noautohide panel with backdrag", + attrs: { noautohide: true, backdrag: "true" }, + test: function(panel) { + var label = document.createElement("label"); + label.id = "backdragspot"; + label.setAttribute("value", "Hello There"); + panel.appendChild(label); + panel.openPopupAtScreen(200, 230); + }, + result: function(testname, panel) { + var oldrect = panel.getOuterScreenRect(); + + // Linux uses native window moving + if (navigator.platform.indexOf("Linux") == -1) { + var backdragspot = document.getElementById("backdragspot"); + synthesizeMouse(backdragspot, 5, 5, { type: "mousedown" }); + synthesizeMouse(backdragspot, 15, 20, { type: "mousemove" }); + synthesizeMouse(backdragspot, 15, 20, { type: "mouseup" }); + + is(panel.getOuterScreenRect().left, 210, testname + "left"); + is(panel.getOuterScreenRect().top, 245, testname + "top"); + } + } + }, + { + // The panel should be allowed to appear and remain offscreen + testname: "normal panel with flip='none' off-screen", + attrs: { "flip": "none" }, + test: function(panel) { + panel.openPopup(document.documentElement, "", -100 - mozInnerScreenX, -100 - mozInnerScreenY, false, false, null); + }, + result: function(testname, panel) { + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, -100 - mozInnerScreenX, testname + "left"); + is(panelrect.top, -100 - mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, -100, testname + " screen left"); + is(screenRect.top, -100, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, + { + // The panel should be allowed to remain offscreen after moving and it should follow the anchor + testname: "normal panel with flip='none' moved off-screen", + attrs: { "flip": "none" }, + test: function(panel) { + panel.openPopup(document.documentElement, "", -100 - mozInnerScreenX, -100 - mozInnerScreenY, false, false, null); + window.moveBy(-50, -50); + }, + result: function(testname, panel) { + if (navigator.platform.indexOf("Linux") >= 0) { + // The window position doesn't get updated immediately on Linux. + return; + } + var panelrect = panel.getBoundingClientRect(); + is(panelrect.left, -150 - mozInnerScreenX, testname + "left"); + is(panelrect.top, -150 - mozInnerScreenY, testname + "top"); + is(panelrect.width, 120, testname + "width"); + is(panelrect.height, 40, testname + "height"); + + var screenRect = panel.getOuterScreenRect(); + is(screenRect.left, -150, testname + " screen left"); + is(screenRect.top, -150, testname + " screen top"); + is(screenRect.width, 120, testname + " screen width"); + is(screenRect.height, 40, testname + " screen height"); + } + }, +]; + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(test_panels, window); + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/window_panel_focus.xul b/toolkit/content/tests/chrome/window_panel_focus.xul new file mode 100644 index 0000000000..6ac1abdc0b --- /dev/null +++ b/toolkit/content/tests/chrome/window_panel_focus.xul @@ -0,0 +1,132 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Panel Focus Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<checkbox id="b1" label="Item 1"/> + +<!-- Focus should be in this order: 2 6 3 8 1 4 5 7 9 --> +<panel id="panel" norestorefocus="true" onpopupshown="panelShown()" onpopuphidden="panelHidden()"> + <button id="t1" label="Button One"/> + <button id="t2" tabindex="1" label="Button Two" onblur="gButtonBlur++;"/> + <button id="t3" tabindex="2" label="Button Three"/> + <button id="t4" tabindex="0" label="Button Four"/> + <button id="t5" label="Button Five"/> + <button id="t6" tabindex="1" label="Button Six"/> + <button id="t7" label="Button Seven"/> + <button id="t8" tabindex="4" label="Button Eight"/> + <button id="t9" label="Button Nine"/> +</panel> + +<panel id="noautofocusPanel" noautofocus="true" + onpopupshown="noautofocusPanelShown()" onpopuphidden="noautofocusPanelHidden()"> + <textbox id="tb3"/> +</panel> + +<checkbox id="b2" label="Item 2" popup="panel" onblur="gButtonBlur++;"/> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var gButtonBlur = 0; + +function showPanel() +{ + // click on the document so that the window has focus + synthesizeMouse(document.documentElement, 1, 1, { }); + + // focus the button + synthesizeKeyExpectEvent("VK_TAB", { }, $("b1"), "focus", "button focus"); + // tabbing again should skip the popup + synthesizeKeyExpectEvent("VK_TAB", { }, $("b2"), "focus", "popup skipped in focus navigation"); + + $("panel").openPopup(null, "", 10, 10, false, false); +} + +function panelShown() +{ + // the focus on the button should have been removed when the popup was opened + is(gButtonBlur, 1, "focus removed when popup opened"); + + // press tab numerous times to cycle through the buttons. The t2 button will + // be blurred twice, so gButtonBlur will be 3 afterwards. + synthesizeKeyExpectEvent("VK_TAB", { }, $("t2"), "focus", "tabindex 1"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t6"), "focus", "tabindex 2"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t3"), "focus", "tabindex 3"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t8"), "focus", "tabindex 4"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t1"), "focus", "tabindex 5"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t4"), "focus", "tabindex 6"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t5"), "focus", "tabindex 7"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t7"), "focus", "tabindex 8"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t9"), "focus", "tabindex 9"); + synthesizeKeyExpectEvent("VK_TAB", { }, $("t2"), "focus", "tabindex 10"); + + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t9"), "focus", "back tabindex 1"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t7"), "focus", "back tabindex 2"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t5"), "focus", "back tabindex 3"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t4"), "focus", "back tabindex 4"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t1"), "focus", "back tabindex 5"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t8"), "focus", "back tabindex 6"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t3"), "focus", "back tabindex 7"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t6"), "focus", "back tabindex 8"); + synthesizeKeyExpectEvent("VK_TAB", { shiftKey: true }, $("t2"), "focus", "back tabindex 9"); + + is(gButtonBlur, 3, "blur events fired within popup"); + + synthesizeKey("VK_ESCAPE", { }); +} + +function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + window.opener.wrappedJSObject.SimpleTest.is(left, right, message); +} + +function panelHidden() +{ + // closing the popup should have blurred the focused element + is(gButtonBlur, 4, "focus removed when popup closed"); + + // now that the panel is hidden, pressing tab should focus the elements in + // the main window again + synthesizeKeyExpectEvent("VK_TAB", { }, $("b1"), "focus", "focus after popup closed"); + + $("noautofocusPanel").openPopup(null, "", 10, 10, false, false); +} + +function noautofocusPanelShown() +{ + // with noautofocus="true", the focus should not be removed when the panel is + // opened, so key events should still be fired at the checkbox. + synthesizeKeyExpectEvent("VK_SPACE", { }, $("b1"), "command", "noautofocus"); + $("noautofocusPanel").hidePopup(); +} + +function noautofocusPanelHidden() +{ + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(showPanel, window); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_anchor.xul b/toolkit/content/tests/chrome/window_popup_anchor.xul new file mode 100644 index 0000000000..45f5fe365a --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_anchor.xul @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Anchor Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<script> +function runTests() +{ + frames[0].openPopup(); +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window); +</script> + +<spacer height="13"/> +<button id="outerbutton" label="Button One" style="margin-left: 6px; -moz-appearance: none;"/> +<hbox> + <spacer width="20"/> + <deck> + <vbox> + <iframe id="frame" style="margin-left: 60px; margin-top: 10px; border-left: 17px solid red; padding-left: 0 !important; padding-top: 3px;" + width="250" height="80" src="frame_popup_anchor.xul"/> + </vbox> + </deck> +</hbox> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_anchoratrect.xul b/toolkit/content/tests/chrome/window_popup_anchoratrect.xul new file mode 100644 index 0000000000..ff37afee75 --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_anchoratrect.xul @@ -0,0 +1,117 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onpopupshown="popupshown(event.target)" onpopuphidden="nextTest()"> + +<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<label value="Popup Test"/> + +<menupopup id="popup"> + <menuitem label="One"/> + <menuitem label="Two"/> +</menupopup> + +<panel id="panel" noautohide="true" height="20"> + <label value="OK"/> +</panel> + +<script> +<![CDATA[ + +let menupopup; + +let tests = [ + { + test: () => menupopup.openPopupAtScreenRect("after_start", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + is(rect.left, 150, "popup at screen position x"); + is(rect.top, 290, "popup at screen position y"); + } + }, + { + test: () => menupopup.openPopupAtScreenRect("after_start", 150, 350, 30, 9000), + verify: popup => { + let rect = popup.getOuterScreenRect(); + is(rect.left, 150, "flipped popup at screen position x"); + is(rect.bottom, 350, "flipped popup at screen position y"); + } + }, + { + test: () => menupopup.openPopupAtScreenRect("end_before", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + is(rect.left, 180, "popup at end_before screen position x"); + is(rect.top, 250, "popup at end_before screen position y"); + } + }, + { + test: () => $("panel").openPopupAtScreenRect("after_start", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + is(rect.left, 150, "panel at screen position x"); + is(rect.top, 290, "panel at screen position y"); + } + }, + { + test: () => $("panel").openPopupAtScreenRect("before_start", 150, 250, 30, 40), + verify: popup => { + let rect = popup.getOuterScreenRect(); + is(rect.left, 150, "panel at before_start screen position x"); + is(rect.bottom, 250, "panel at before_start screen position y"); + } + }, +]; + +function runTest(id) +{ + menupopup = $("popup"); + nextTest(); +} + +function nextTest() +{ + if (!tests.length) { + window.close(); + window.opener.SimpleTest.finish(); + return; + } + + tests[0].test(); +} + +function popupshown(popup) +{ + tests[0].verify(popup); + tests.shift(); + popup.hidePopup(); +} + +function is(left, right, message) +{ + window.opener.SimpleTest.is(left, right, message); +} + +function ok(value, message) +{ + window.opener.SimpleTest.ok(value, message); +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_attribute.xul b/toolkit/content/tests/chrome/window_popup_attribute.xul new file mode 100644 index 0000000000..9316d31c4a --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_attribute.xul @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Attribute Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + <script type="application/javascript" src="popup_trigger.js"></script> + +<script> +window.opener.SimpleTest.waitForFocus(runTests, window); +</script> + +<hbox style="margin-left: 200px; margin-top: 270px;"> + <label id="trigger" popup="thepopup" value="Popup" height="60"/> +</hbox> +<!-- this frame is used to check that document.popupNode + is inaccessible from different sources --> +<iframe id="childframe" type="content" width="10" height="10" + src="http://sectest2.example.org:80/chrome/toolkit/content/tests/chrome/popup_childframe_node.xul"/> + +<menupopup id="thepopup"> + <menuitem id="item1" label="First"/> + <menuitem id="item2" label="Main Item"/> + <menuitem id="amenu" label="A Menu" accesskey="M"/> + <menuitem id="item3" label="Third"/> + <menuitem id="one" label="One"/> + <menuitem id="fancier" label="Fancier Menu"/> + <menu id="submenu" label="Only Menu"> + <menupopup id="submenupopup"> + <menuitem id="submenuitem" label="Test Submenu"/> + </menupopup> + </menu> + <menuitem id="other" disabled="true" label="Other Menu"/> + <menuitem id="secondlast" label="Second Last Menu" accesskey="T"/> + <menuitem id="last" label="One Other Menu"/> +</menupopup> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_button.xul b/toolkit/content/tests/chrome/window_popup_button.xul new file mode 100644 index 0000000000..125e6886cb --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_button.xul @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + <script type="application/javascript" src="popup_trigger.js"></script> + +<script> +window.opener.SimpleTest.waitForFocus(runTests, window); +</script> + +<hbox style="margin-left: 200px; margin-top: 270px;"> + <button id="trigger" type="menu" label="Popup" width="100" height="50"> + <menupopup id="thepopup"> + <menuitem id="item1" label="First"/> + <menuitem id="item2" label="Main Item"/> + <menuitem id="amenu" label="A Menu" accesskey="M"/> + <menuitem id="item3" label="Third"/> + <menuitem id="one" label="One"/> + <menuitem id="fancier" label="Fancier Menu"/> + <menu id="submenu" label="Only Menu"> + <menupopup id="submenupopup"> + <menuitem id="submenuitem" label="Test Submenu"/> + </menupopup> + </menu> + <menuitem id="other" disabled="true" label="Other Menu"/> + <menuitem id="secondlast" label="Second Last Menu" accesskey="T"/> + <menuitem id="last" label="One Other Menu"/> + </menupopup> + </button> +</hbox> + +<!-- this frame is used to check that document.popupNode + is inaccessible from different sources --> +<iframe id="childframe" type="content" width="10" height="10" + src="http://sectest2.example.org:80/chrome/toolkit/content/tests/chrome/popup_childframe_node.xul"/> + +</window> diff --git a/toolkit/content/tests/chrome/window_popup_preventdefault_chrome.xul b/toolkit/content/tests/chrome/window_popup_preventdefault_chrome.xul new file mode 100644 index 0000000000..4d10d7fc75 --- /dev/null +++ b/toolkit/content/tests/chrome/window_popup_preventdefault_chrome.xul @@ -0,0 +1,113 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="Popup Prevent Default Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + +<!-- + This tests checks that preventDefault can be called on a popupshowing + event or popuphiding event to prevent the default behaviour. + --> + +<script> + +var gBlockShowing = true; +var gBlockHiding = true; +var gShownNotAllowed = true; +var gHiddenNotAllowed = true; + +var fm = Components.classes["@mozilla.org/focus-manager;1"]. + getService(Components.interfaces.nsIFocusManager); + +var is = function(l, r, v) { window.opener.wrappedJSObject.SimpleTest.is(l, r, v); } +var isnot = function(l, r, v) { window.opener.wrappedJSObject.SimpleTest.isnot(l, r, v); } + +function runTest() +{ + var menu = document.getElementById("menu"); + + is(fm.activeWindow, window, "active window at start"); + is(fm.focusedWindow, window, "focused window at start"); + + is(window.windowState, window.STATE_NORMAL, "window is normal"); + // the minimizing test sometimes fails on Linux so don't test it there + if (navigator.platform.indexOf("Lin") == 0) { + menu.open = true; + return; + } + window.minimize(); + is(window.windowState, window.STATE_MINIMIZED, "window is minimized"); + + isnot(fm.activeWindow, window, "active window after minimize"); + isnot(fm.focusedWindow, window, "focused window after minimize"); + + menu.open = true; + + setTimeout(runTestAfterMinimize, 0); +} + +function runTestAfterMinimize() +{ + var menu = document.getElementById("menu"); + is(menu.firstChild.state, "closed", "popup not opened when window minimized"); + + window.restore(); + is(window.windowState, window.STATE_NORMAL, "window is restored"); + + is(fm.activeWindow, window, "active window after restore"); + is(fm.focusedWindow, window, "focused window after restore"); + + menu.open = true; +} + +function popupShowing(event) +{ + if (gBlockShowing) { + event.preventDefault(); + gBlockShowing = false; + setTimeout(function() { + gShownNotAllowed = false; + document.getElementById("menu").open = true; + }, 3000, true); + } +} + +function popupShown() +{ + window.opener.wrappedJSObject.SimpleTest.ok(!gShownNotAllowed, "popupshowing preventDefault"); + document.getElementById("menu").open = false; +} + +function popupHiding(event) +{ + if (gBlockHiding) { + event.preventDefault(); + gBlockHiding = false; + setTimeout(function() { + gHiddenNotAllowed = false; + document.getElementById("menu").open = false; + }, 3000, true); + } +} + +function popupHidden() +{ + window.opener.wrappedJSObject.SimpleTest.ok(!gHiddenNotAllowed, "popuphiding preventDefault"); + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window); +</script> + +<button id="menu" type="menu" label="Menu"> + <menupopup onpopupshowing="popupShowing(event);" + onpopupshown="popupShown();" + onpopuphiding="popupHiding(event);" + onpopuphidden="popupHidden();"> + <menuitem label="Item"/> + </menupopup> +</button> + + +</window> diff --git a/toolkit/content/tests/chrome/window_preferences.xul b/toolkit/content/tests/chrome/window_preferences.xul new file mode 100644 index 0000000000..25ee4b5b29 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences.xul @@ -0,0 +1,73 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window +--> +<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="preferences window" + windowtype="test:preferences" + buttons="accept,cancel" + onload="RunTest(window.arguments)" +> + <script type="application/javascript"> + <![CDATA[ + function RunTest(aArgs) + { + // run test + aArgs[0](this); + // close dialog + document.documentElement[aArgs[1] ? "acceptDialog" : "cancelDialog"](); + } + ]]> + </script> + + <prefpane id="sample_pane" label="Sample Prefpane"> + <preferences id="sample_preferences"> + <!-- one of each type known to <preferences>.valueFromPreferences --> + <preference id ="tests.static_preference_int" + name="tests.static_preference_int" + type="int"/> + <preference id ="tests.static_preference_bool" + name="tests.static_preference_bool" + type="bool"/> + <preference id ="tests.static_preference_string" + name="tests.static_preference_string" + type="string"/> + <preference id ="tests.static_preference_wstring" + name="tests.static_preference_wstring" + type="wstring"/> + <preference id ="tests.static_preference_unichar" + name="tests.static_preference_unichar" + type="unichar"/> + <preference id ="tests.static_preference_file" + name="tests.static_preference_file" + type="file"/> + </preferences> + + <!-- one element for each preference type above --> + <hbox> + <label flex="1" value="int"/> + <textbox id="static_element_int" preference="tests.static_preference_int"/> + </hbox> + <hbox> + <label flex="1" value="bool"/> + <checkbox id="static_element_bool" preference="tests.static_preference_bool"/> + </hbox> + <hbox> + <label flex="1" value="string"/> + <textbox id="static_element_string" preference="tests.static_preference_string"/> + </hbox> + <hbox> + <label flex="1" value="wstring"/> + <textbox id="static_element_wstring" preference="tests.static_preference_wstring"/> + </hbox> + <hbox> + <label flex="1" value="unichar"/> + <textbox id="static_element_unichar" preference="tests.static_preference_unichar"/> + </hbox> + <hbox> + <label flex="1" value="file"/> + <textbox id="static_element_file" preference="tests.static_preference_file"/> + </hbox> + </prefpane> +</prefwindow> diff --git a/toolkit/content/tests/chrome/window_preferences2.xul b/toolkit/content/tests/chrome/window_preferences2.xul new file mode 100644 index 0000000000..87158c9c7c --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences2.xul @@ -0,0 +1,25 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window +--> +<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="preferences window" + windowtype="test:preferences2" + buttons="accept,cancel" + onload="RunTest(window.arguments)" +> + <script type="application/javascript"> + <![CDATA[ + function RunTest(aArgs) + { + // open child + document.documentElement.openSubDialog("window_preferences3.xul", "", {test: aArgs[0], accept: aArgs[1]}); + // close dialog + document.documentElement[aArgs[1] ? "acceptDialog" : "cancelDialog"](); + } + ]]> + </script> + + <prefpane id="sample_pane" label="Sample Prefpane"/> +</prefwindow> diff --git a/toolkit/content/tests/chrome/window_preferences3.xul b/toolkit/content/tests/chrome/window_preferences3.xul new file mode 100644 index 0000000000..c37893a678 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences3.xul @@ -0,0 +1,74 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window +--> +<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="preferences window" + windowtype="test:preferences3" + buttons="accept,cancel" + onload="RunTest(window.arguments)" + type="child" +> + <script type="application/javascript"> + <![CDATA[ + function RunTest(aArgs) + { + // run test + aArgs[0].test(this); + // close dialog + document.documentElement[aArgs[0].accept ? "acceptDialog" : "cancelDialog"](); + } + ]]> + </script> + + <prefpane id="sample_pane" label="Sample Prefpane"> + <preferences id="sample_preferences"> + <!-- one of each type known to <preferences>.valueFromPreferences --> + <preference id ="tests.static_preference_int" + name="tests.static_preference_int" + type="int"/> + <preference id ="tests.static_preference_bool" + name="tests.static_preference_bool" + type="bool"/> + <preference id ="tests.static_preference_string" + name="tests.static_preference_string" + type="string"/> + <preference id ="tests.static_preference_wstring" + name="tests.static_preference_wstring" + type="wstring"/> + <preference id ="tests.static_preference_unichar" + name="tests.static_preference_unichar" + type="unichar"/> + <preference id ="tests.static_preference_file" + name="tests.static_preference_file" + type="file"/> + </preferences> + + <!-- one element for each preference type above --> + <hbox> + <label flex="1" value="int"/> + <textbox id="static_element_int" preference="tests.static_preference_int"/> + </hbox> + <hbox> + <label flex="1" value="bool"/> + <checkbox id="static_element_bool" preference="tests.static_preference_bool"/> + </hbox> + <hbox> + <label flex="1" value="string"/> + <textbox id="static_element_string" preference="tests.static_preference_string"/> + </hbox> + <hbox> + <label flex="1" value="wstring"/> + <textbox id="static_element_wstring" preference="tests.static_preference_wstring"/> + </hbox> + <hbox> + <label flex="1" value="unichar"/> + <textbox id="static_element_unichar" preference="tests.static_preference_unichar"/> + </hbox> + <hbox> + <label flex="1" value="file"/> + <textbox id="static_element_file" preference="tests.static_preference_file"/> + </hbox> + </prefpane> +</prefwindow> diff --git a/toolkit/content/tests/chrome/window_preferences_beforeaccept.xul b/toolkit/content/tests/chrome/window_preferences_beforeaccept.xul new file mode 100644 index 0000000000..ba200b6149 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_beforeaccept.xul @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window with beforeaccept +--> +<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="preferences window" + width="300" height="300" + windowtype="test:preferences" + buttons="accept,cancel" + onbeforeaccept="return beforeAccept();" + onload="onDialogLoad();" +> + <script type="application/javascript"> + <![CDATA[ + function onDialogLoad() { + var pref = document.getElementById("tests.beforeaccept.dialogShown"); + pref.value = true; + + // call the onload handler we were passed + window.arguments[0](); + } + + function beforeAccept() { + var beforeAcceptPref = document.getElementById("tests.beforeaccept.called"); + var oldValue = beforeAcceptPref.value; + beforeAcceptPref.value = true; + + return !!oldValue; + } + ]]> + </script> + + <prefpane id="sample_pane" label="Sample Prefpane"> + <preferences id="sample_preferences"> + <preference id="tests.beforeaccept.called" + name="tests.beforeaccept.called" + type="bool"/> + <preference id="tests.beforeaccept.dialogShown" + name="tests.beforeaccept.dialogShown" + type="bool"/> + </preferences> + </prefpane> + <label>Test Prefpane</label> +</prefwindow> diff --git a/toolkit/content/tests/chrome/window_preferences_commandretarget.xul b/toolkit/content/tests/chrome/window_preferences_commandretarget.xul new file mode 100644 index 0000000000..77c6fd18ce --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_commandretarget.xul @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for preferences window. This particular test ensures that + a checkbox with a command attribute properly updates even though the command + event gets retargeted. +--> +<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="preferences window" + windowtype="test:preferences" + buttons="accept,cancel" + onload="RunTest(window.arguments)"> + <script type="application/javascript"> + <![CDATA[ + function RunTest(aArgs) + { + aArgs[0](this); + document.documentElement.cancelDialog(); + } + ]]> + </script> + + <prefpane id="sample_pane" label="Sample Prefpane"> + <preferences id="sample_preferences"> + <preference id="tests.static_preference_bool" + name="tests.static_preference_bool" + type="bool"/> + </preferences> + + <commandset> + <command id="cmd_test" preference="tests.static_preference_bool"/> + </commandset> + + <checkbox id="checkbox" label="Enable Option" preference="tests.static_preference_bool" command="cmd_test"/> + </prefpane> +</prefwindow> diff --git a/toolkit/content/tests/chrome/window_preferences_onsyncfrompreference.xul b/toolkit/content/tests/chrome/window_preferences_onsyncfrompreference.xul new file mode 100644 index 0000000000..e0366f9895 --- /dev/null +++ b/toolkit/content/tests/chrome/window_preferences_onsyncfrompreference.xul @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- 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/. --> +<!-- + XUL Widget Test for preferences window with onsyncfrompreference + This test ensures that onsyncfrompreference handlers are called after all the + values of the corresponding preference element have been set correctly +--> +<prefwindow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="preferences window" + width="300" height="300" + windowtype="test:preferences"> + + <prefpane id="sample_pane" label="Sample Prefpane"> + <preferences id="sample_preferences"> + <preference id="tests.onsyncfrompreference.pref1" + name="tests.onsyncfrompreference.pref1" + type="int"/> + <preference id="tests.onsyncfrompreference.pref2" + name="tests.onsyncfrompreference.pref2" + type="int"/> + <preference id="tests.onsyncfrompreference.pref3" + name="tests.onsyncfrompreference.pref3" + type="int"/> + </preferences> + </prefpane> + <label>Test Prefpane</label> + <checkbox id="check1" label="Label1" + preference="tests.onsyncfrompreference.pref1" + onsyncfrompreference="return window.arguments[0]();" + onsynctopreference="return 1;"/> + <checkbox id="check2" label="Label2" + preference="tests.onsyncfrompreference.pref2" + onsyncfrompreference="return window.arguments[0]();" + onsynctopreference="return 1;"/> + <checkbox id="check3" label="Label3" + preference="tests.onsyncfrompreference.pref3" + onsyncfrompreference="return window.arguments[0]();" + onsynctopreference="return 1;"/> +</prefwindow> diff --git a/toolkit/content/tests/chrome/window_screenPosSize.xul b/toolkit/content/tests/chrome/window_screenPosSize.xul new file mode 100644 index 0000000000..accc10d8f1 --- /dev/null +++ b/toolkit/content/tests/chrome/window_screenPosSize.xul @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Window Open Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + screenX="80" + screenY="80" + height="300" + width="300" + persist="screenX screenY height width"> + +<body xmlns="http://www.w3.org/1999/xhtml"> + +</body> + +</window> diff --git a/toolkit/content/tests/chrome/window_showcaret.xul b/toolkit/content/tests/chrome/window_showcaret.xul new file mode 100644 index 0000000000..cb26658a11 --- /dev/null +++ b/toolkit/content/tests/chrome/window_showcaret.xul @@ -0,0 +1,10 @@ +<?xml version='1.0'?> + +<?xml-stylesheet href='chrome://global/skin' type='text/css'?> + +<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'> + +<hbox style='-moz-user-focus: normal;' width='20' height='20'/> +<textbox/> + +</window> diff --git a/toolkit/content/tests/chrome/window_subframe_origin.xul b/toolkit/content/tests/chrome/window_subframe_origin.xul new file mode 100644 index 0000000000..a060929a64 --- /dev/null +++ b/toolkit/content/tests/chrome/window_subframe_origin.xul @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!-- 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/. --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="window" title="Subframe Origin Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> +<script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<iframe + style="margin-left:20px; margin-top:20px; min-height:300px; max-width:300px; max-height:300px; border:solid 1px black;" + src="frame_subframe_origin_subframe1.xul"></iframe> +<caption id="parentcap" label=""/> + +<script> + +// Fire a mouse move event aimed at this window, and check to be +// sure the client coords translate from widget to the dom correctly. + +function runTests() +{ + synthesizeMouse(document.getElementById("window"), 1, 2, { type: "mousemove" }); +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTests, window); + +function mouseMove(e) { + var element = e.target; + var el = document.getElementById("parentcap"); + el.label = "client: (" + e.clientX + "," + e.clientY + ")"; + window.opener.wrappedJSObject.SimpleTest.is(e.clientX, 1, "mouse event clientX"); + window.opener.wrappedJSObject.SimpleTest.is(e.clientY, 2, "mouse event clientY"); + // fire the next test on the sub frame + frames[0].runTests(); +} + +window.addEventListener("mousemove",mouseMove, false); + +</script> +</window> diff --git a/toolkit/content/tests/chrome/window_titlebar.xul b/toolkit/content/tests/chrome/window_titlebar.xul new file mode 100644 index 0000000000..e27782153b --- /dev/null +++ b/toolkit/content/tests/chrome/window_titlebar.xul @@ -0,0 +1,223 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<!-- + XUL Widget Test for the titlebar element and window dragging + --> +<window title="Titlebar" width="200" height="200" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + + <titlebar id="titlebar"> + <label id="label" value="Titlebar"/> + </titlebar> + + <!-- a non-noautohide panel is treated as anchored --> + <panel id="panel" onpopupshown="popupshown(this, false)" onpopuphidden="popuphidden('panelnoautohide')"> + <titlebar> + <label id="panellabel" value="Titlebar"/> + </titlebar> + </panel> + + <panel id="panelnoautohide" noautohide="true" + onpopupshown="popupshown(this, false)" onpopuphidden="popuphidden('panelanchored')"> + <titlebar> + <label id="panellabelnoautohide" value="Titlebar"/> + </titlebar> + </panel> + + <panel id="panelanchored" noautohide="true" + onpopupshown="popupshown(this, true)" onpopuphidden="popuphidden('paneltop')"> + <titlebar> + <label id="panellabelanchored" value="Titlebar"/> + </titlebar> + </panel> + + <panel id="paneltop" noautohide="true" level="top" + onpopupshown="popupshown(this, false)" onpopuphidden="popuphidden('panelfloating')"> + <titlebar> + <label id="panellabeltop" value="Titlebar"/> + </titlebar> + </panel> + + <panel id="panelfloating" noautohide="true" level="floating" + onpopupshown="popupshown(this, false)" onpopuphidden="popuphidden('')"> + <titlebar> + <label id="panellabelfloating" value="Titlebar"/> + </titlebar> + </panel> + + <button id="button" label="OK"/> + +<script> +<![CDATA[ + +var SimpleTest = window.opener.wrappedJSObject.SimpleTest; + +SimpleTest.waitForFocus(test_titlebar, window); + +var mouseDownTarget; +var origoldx, origoldy, oldx, oldy, waitSteps = 0; +function waitForWindowMove(element, x, y, callback, arg, panel, anchored) +{ + var isPanelMove = (element.id != "label"); + + if (!waitSteps) { + oldx = isPanelMove ? panel.getBoundingClientRect().left : window.screenX; + oldy = isPanelMove ? panel.getBoundingClientRect().top : window.screenY; + synthesizeMouse(element, x, y, { type: "mousemove" }); + } + + var newx = isPanelMove ? panel.getBoundingClientRect().left : window.screenX; + var newy = isPanelMove ? panel.getBoundingClientRect().top : window.screenY; + if (newx == oldx && newy == oldy) { + if (waitSteps++ > 10) { + SimpleTest.is(window.screenX + "," + window.screenY, oldx + "," + oldy + " ", + "Window never moved properly to " + x + "," + y + (panel ? " " + panel.id : "")); + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); + return; + } + + setTimeout(waitForWindowMove, 100, element, x, y, callback, arg, panel, anchored); + } + else { + waitSteps = 0; + + // on Linux, we need to wait a bit for the popup to be moved as well + if (navigator.platform.indexOf("Linux") >= 0) { + setTimeout(callback, 0, arg, panel, anchored); + } + else { + callback(arg, panel, anchored); + } + } +} + +function test_titlebar() +{ + var titlebar = document.getElementById("titlebar"); + var label = document.getElementById("label"); + + origoldx = window.screenX; + origoldy = window.screenY; + + var mousedownListener = event => mouseDownTarget = event.originalTarget; + window.addEventListener("mousedown", mousedownListener, false); + synthesizeMouse(label, 2, 2, { type: "mousedown" }); + SimpleTest.is(mouseDownTarget, titlebar, "movedown on titlebar"); + waitForWindowMove(label, 22, 22, test_titlebar_step2, mousedownListener); +} + +function test_titlebar_step2(mousedownListener) +{ + var titlebar = document.getElementById("titlebar"); + var label = document.getElementById("label"); + + SimpleTest.is(window.screenX, origoldx + 20, "move window horizontal"); + SimpleTest.is(window.screenY, origoldy + 20, "move window vertical"); + synthesizeMouse(label, 22, 22, { type: "mouseup" }); + + // with allowEvents set to true, the mouse should target the label instead + // and not move the window + titlebar.allowEvents = true; + + synthesizeMouse(label, 2, 2, { type: "mousedown" }); + SimpleTest.is(mouseDownTarget, label, "movedown on titlebar with allowevents"); + synthesizeMouse(label, 22, 22, { type: "mousemove" }); + SimpleTest.is(window.screenX, origoldx + 20, "mouse on label move window horizontal"); + SimpleTest.is(window.screenY, origoldy + 20, "mouse on label move window vertical"); + synthesizeMouse(label, 22, 22, { type: "mouseup" }); + + window.removeEventListener("mousedown", mousedownListener, false); + + document.getElementById("panel").openPopupAtScreen(window.screenX + 50, window.screenY + 60, false); +} + +function popupshown(panel, anchored) +{ + var rect = panel.getBoundingClientRect(); + + // skip this check for non-noautohide panels + if (panel.id == "panel") { + var panellabel = panel.firstChild.firstChild; + synthesizeMouse(panellabel, 2, 2, { type: "mousedown" }); + waitForWindowMove(panellabel, 22, 22, popupshown_step3, rect, panel, anchored); + return; + } + + // now, try moving the window. If anchored, the popup should move with the + // window. If not anchored, the popup should remain at its current screen location. + window.moveBy(10, 10); + waitSteps = 1; + waitForWindowMove(document.getElementById("label"), 1, 1, popupshown_step2, rect, panel, anchored); +} + +function popupshown_step2(oldrect, panel, anchored) +{ + var newrect = panel.getBoundingClientRect(); + + // The window movement that occured long ago at the beginning of the test + // on Linux is delayed and there isn't any way to tell when the move + // actually happened. This causes the checks here to fail. Instead, just + // wait a bit for the test to be ready. + if (navigator.platform.indexOf("Linux") >= 0 && + newrect.left != oldrect.left - (anchored ? 0 : 10)) { + setTimeout(popupshown_step2, 10, oldrect, panel, anchored); + return; + } + + // anchored popups should still be at the same offset. Non-anchored popups will + // now be offset by 10 pixels less. + SimpleTest.is(newrect.left, oldrect.left - (anchored ? 0 : 10), + panel.id + " horizontal after window move"); + SimpleTest.is(newrect.top, oldrect.top - (anchored ? 0 : 10), + panel.id + " vertical after window move"); + + var panellabel = panel.firstChild.firstChild; + synthesizeMouse(panellabel, 2, 2, { type: "mousedown" }); + waitForWindowMove(panellabel, 22, 22, popupshown_step3, newrect, panel, anchored); +} + +function popupshown_step3(oldrect, panel, anchored) +{ + // skip this check on Linux for the same window positioning reasons as above + if (navigator.platform.indexOf("Linux") == -1 || (panel.id != "panelanchored" && panel.id != "paneltop")) { + // next, drag the titlebar in the panel + var newrect = panel.getBoundingClientRect(); + SimpleTest.is(newrect.left, oldrect.left + 20, panel.id + " move popup horizontal"); + SimpleTest.is(newrect.top, oldrect.top + 20, panel.id + " move popup vertical"); + synthesizeMouse(document.getElementById("panellabel"), 22, 22, { type: "mouseup" }); + + synthesizeMouse(document.getElementById("button"), 5, 5, { type: "mousemove" }); + newrect = panel.getBoundingClientRect(); + SimpleTest.is(newrect.left, oldrect.left + 20, panel.id + " horizontal after mouse on button"); + SimpleTest.is(newrect.top, oldrect.top + 20, panel.id + " vertical after mouse on button"); + } + else { + synthesizeMouse(document.getElementById("panellabel"), 22, 22, { type: "mouseup" }); + } + + panel.hidePopup(); +} + +function popuphidden(nextPopup) +{ + if (nextPopup) { + var panel = document.getElementById(nextPopup); + if (panel.id == "panelnoautohide") { + panel.openPopupAtScreen(window.screenX + 50, window.screenY + 60, false); + } + else { + panel.openPopup(document.getElementById("button"), "after_start"); + } + } + else + window.opener.wrappedJSObject.done(window); +} + +]]> +</script> + +</window> diff --git a/toolkit/content/tests/chrome/window_tooltip.xul b/toolkit/content/tests/chrome/window_tooltip.xul new file mode 100644 index 0000000000..087c91c3e7 --- /dev/null +++ b/toolkit/content/tests/chrome/window_tooltip.xul @@ -0,0 +1,311 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Tooltip Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + +<tooltip id="thetooltip"> + <label id="label" value="This is a tooltip"/> +</tooltip> + +<box id="parent" tooltiptext="Box Tooltip" style="margin: 10px"> + <button id="withtext" label="Tooltip Text" tooltiptext="Button Tooltip" + style="-moz-appearance: none; padding: 0;"/> + <button id="without" label="No Tooltip" style="-moz-appearance: none; padding: 0;"/> + <!-- remove the native theme and borders to avoid some platform + specific sizing differences --> + <button id="withtooltip" label="Tooltip Element" tooltip="thetooltip" + class="plain" style="-moz-appearance: none; padding: 0;"/> + <iframe id="childframe" type="content" width="10" height="10" + src="http://sectest2.example.org:80/chrome/toolkit/content/tests/chrome/popup_childframe_node.xul"/> +</box> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var gOriginalWidth = -1; +var gOriginalHeight = -1; +var gButton = null; + +function runTest() +{ + startPopupTests(popupTests); +} + +function checkCoords(event) +{ + // all but one test open the tooltip at the button location offset by 6 + // in each direction. Test 5 opens it at 4 in each direction. + var mod = (gTestIndex == 5) ? 4 : 6; + + var rect = gButton.getBoundingClientRect(); + var popupstyle = window.getComputedStyle(gButton, ""); + is(event.clientX, Math.round(rect.left + mod), + "step " + (gTestIndex + 1) + " clientX"); + is(event.clientY, Math.round(rect.top + mod), + "step " + (gTestIndex + 1) + " clientY"); + ok(event.screenX > 0, "step " + (gTestIndex + 1) + " screenX"); + ok(event.screenY > 0, "step " + (gTestIndex + 1) + " screenY"); +} + +var popupTests = [ +{ + testname: "hover tooltiptext attribute", + events: [ "popupshowing #tooltip", "popupshown #tooltip" ], + test: function() { + gButton = document.getElementById("withtext"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + } +}, +{ + testname: "close tooltip", + events: [ "popuphiding #tooltip", "popuphidden #tooltip", + "DOMMenuInactive #tooltip" ], + test: function() { + disableNonTestMouse(true); + synthesizeMouse(document.documentElement, 2, 2, { type: "mousemove" }); + disableNonTestMouse(false); + } +}, +{ + testname: "hover inherited tooltip", + events: [ "popupshowing #tooltip", "popupshown #tooltip" ], + test: function() { + gButton = document.getElementById("without"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + } +}, +{ + testname: "hover tooltip attribute", + events: [ "popuphiding #tooltip", "popuphidden #tooltip", + "DOMMenuInactive #tooltip", + "popupshowing thetooltip", "popupshown thetooltip" ], + test: function() { + gButton = document.getElementById("withtooltip"); + gExpectedTriggerNode = gButton; + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result: function(testname) { + var tooltip = document.getElementById("thetooltip"); + gExpectedTriggerNode = null; + is(tooltip.triggerNode, gButton, testname + " triggerNode"); + is(document.popupNode, null, testname + " document.popupNode"); + is(document.tooltipNode, gButton, testname + " document.tooltipNode"); + + var child = $("childframe").contentDocument; + var evt = child.createEvent("Event"); + evt.initEvent("click", true, true); + child.documentElement.dispatchEvent(evt); + is(child.documentElement.getAttribute("data"), "xnull", + "cannot get tooltipNode from other document"); + + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = tooltip.getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"), ""); + + is(Math.round(rect.left), + Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 6), + testname + " left position of tooltip"); + is(Math.round(rect.top), + Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 6), + testname + " top position of tooltip"); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + gOriginalWidth = rect.right - rect.left; + gOriginalHeight = rect.bottom - rect.top; + } +}, +{ + testname: "click to close tooltip", + events: [ "popuphiding thetooltip", "popuphidden thetooltip", + "command withtooltip", "DOMMenuInactive thetooltip" ], + test: function() { + gButton = document.getElementById("withtooltip"); + synthesizeMouse(gButton, 2, 2, { }); + }, + result: function(testname) { + var tooltip = document.getElementById("thetooltip"); + is(tooltip.triggerNode, null, testname + " triggerNode"); + is(document.popupNode, null, testname + " document.popupNode"); + is(document.tooltipNode, null, testname + " document.tooltipNode"); + } +}, +{ + testname: "hover tooltip after size increased", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + test: function() { + var label = document.getElementById("label"); + label.removeAttribute("value"); + label.textContent = "This is a longer tooltip than before\nIt has multiple lines\nIt is testing tooltip sizing\n"; + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result: function(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"), ""); + var buttonstyle = window.getComputedStyle(document.getElementById("withtooltip"), ""); + + is(Math.round(rect.left), + Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 4), + testname + " left position of tooltip"); + is(Math.round(rect.top), + Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 4), + testname + " top position of tooltip"); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + // make sure that the tooltip is larger than it was before by just + // checking against the original height plus an arbitrary 15 pixels + ok(gOriginalWidth + 15 < rect.right - rect.left, testname + " tooltip is wider"); + ok(gOriginalHeight + 15 < rect.bottom - rect.top, testname + " tooltip is taller"); + } +}, +{ + testname: "close tooltip with hidePopup", + events: [ "popuphiding thetooltip", "popuphidden thetooltip", + "DOMMenuInactive thetooltip" ], + test: function() { + document.getElementById("thetooltip").hidePopup(); + }, +}, +{ + testname: "hover tooltip after size decreased", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + autohide: "thetooltip", + test: function() { + var label = document.getElementById("label"); + label.value = "This is a tooltip"; + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + disableNonTestMouse(false); + }, + result: function(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"), ""); + var buttonstyle = window.getComputedStyle(document.getElementById("withtooltip"), ""); + + is(Math.round(rect.left), + Math.round(buttonrect.left + parseFloat(popupstyle.marginLeft) + 6), + testname + " left position of tooltip"); + is(Math.round(rect.top), + Math.round(buttonrect.top + parseFloat(popupstyle.marginTop) + 6), + testname + " top position of tooltip"); + + var labelrect = document.getElementById("label").getBoundingClientRect(); + ok(labelrect.right < rect.right, testname + " tooltip width"); + ok(labelrect.bottom < rect.bottom, testname + " tooltip height"); + + is(gOriginalWidth, rect.right - rect.left, testname + " tooltip is original width"); + is(gOriginalHeight, rect.bottom - rect.top, testname + " tooltip is original height"); + } +}, +{ + testname: "hover tooltip at bottom edge of screen", + events: [ "popupshowing thetooltip", "popupshown thetooltip" ], + autohide: "thetooltip", + condition: function() { + // Only checking OSX here because on other platforms popups and tooltips behave the same way + // when there's not enough space to show them below (by flipping vertically) + // However, on OSX most popups are not flipped but tooltips are. + return navigator.platform.indexOf("Mac") > -1; + }, + test: function() { + var buttonRect = document.getElementById("withtext").getBoundingClientRect(); + var windowY = screen.height - + (window.mozInnerScreenY - window.screenY ) - buttonRect.bottom; + + moveWindowTo(window.screenX, windowY, function() { + gButton = document.getElementById("withtooltip"); + disableNonTestMouse(true); + synthesizeMouse(gButton, 2, 2, { type: "mouseover" }); + synthesizeMouse(gButton, 6, 6, { type: "mousemove" }); + synthesizeMouse(gButton, 4, 4, { type: "mousemove" }); + disableNonTestMouse(false); + }); + }, + result: function(testname) { + var buttonrect = document.getElementById("withtooltip").getBoundingClientRect(); + var rect = document.getElementById("thetooltip").getBoundingClientRect(); + var popupstyle = window.getComputedStyle(document.getElementById("thetooltip"), ""); + + is(Math.round(rect.y + rect.height), + Math.round(buttonrect.top + 4 - parseFloat(popupstyle.marginTop)), + testname + " position of tooltip above button"); + } +} + +]; + +var waitSteps = 0; +function moveWindowTo(x, y, callback, arg) +{ + if (!waitSteps) { + oldx = window.screenX; + oldy = window.screenY; + window.moveTo(x, y); + + waitSteps++; + setTimeout(moveWindowTo, 100, x, y, callback, arg); + return; + } + + if (window.screenX == oldx && window.screenY == oldy) { + if (waitSteps++ > 10) { + ok(false, "Window never moved properly to " + x + "," + y); + window.opener.wrappedJSObject.SimpleTest.finish(); + window.close(); + } + + setTimeout(moveWindowTo, 100, x, y, callback, arg); + } + else { + waitSteps = 0; + callback(arg); + } +} + +window.opener.wrappedJSObject.SimpleTest.waitForFocus(runTest, window); +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/chrome/xul_selectcontrol.js b/toolkit/content/tests/chrome/xul_selectcontrol.js new file mode 100644 index 0000000000..d6518c1503 --- /dev/null +++ b/toolkit/content/tests/chrome/xul_selectcontrol.js @@ -0,0 +1,390 @@ +// This script is used to test elements that implement +// nsIDOMXULSelectControlElement. This currently is the following elements: +// listbox, menulist, radiogroup, richlistbox, tabs +// +// flag behaviours that differ for certain elements +// allow-other-value - alternate values for the value property may be used +// besides those in the list +// other-value-clears-selection - alternative values for the value property +// clears the selected item +// selection-required - an item must be selected in the list, unless there +// aren't any to select +// activate-disabled-menuitem - disabled menuitems can be highlighted +// select-keynav-wraps - key navigation over a selectable list wraps +// select-extended-keynav - home, end, page up and page down keys work to +// navigate over a selectable list +// keynav-leftright - key navigation is left/right rather than up/down +// The win:, mac: and gtk: or other prefixes may be used for platform specific behaviour +var behaviours = { + menu: "win:activate-disabled-menuitem activate-disabled-menuitem-mousemove select-keynav-wraps select-extended-keynav", + menulist: "allow-other-value other-value-clears-selection", + listbox: "select-extended-keynav", + richlistbox: "select-extended-keynav", + radiogroup: "select-keynav-wraps dont-select-disabled allow-other-value", + tabs: "select-extended-keynav mac:select-keynav-wraps allow-other-value selection-required keynav-leftright" +}; + +function behaviourContains(tag, behaviour) +{ + var platform = "none:"; + if (navigator.platform.indexOf("Mac") >= 0) + platform = "mac:"; + else if (navigator.platform.indexOf("Win") >= 0) + platform = "win:"; + else if (navigator.platform.indexOf("X") >= 0) + platform = "gtk:"; + + var re = new RegExp("\\s" + platform + behaviour + "\\s|\\s" + behaviour + "\\s"); + return re.test(" " + behaviours[tag] + " "); +} + +function test_nsIDOMXULSelectControlElement(element, childtag, testprefix) +{ + var testid = (testprefix) ? testprefix + " " : ""; + testid += element.localName + " nsIDOMXULSelectControlElement "; + + // editable menulists use the label as the value instead + var firstvalue = "first", secondvalue = "second", fourthvalue = "fourth"; + if (element.localName == "menulist" && element.editable) { + firstvalue = "First Item"; + secondvalue = "Second Item" + fourthvalue = "Fourth Item"; + } + + // 'initial' - check if the initial state of the element is correct + test_nsIDOMXULSelectControlElement_States(element, testid + "initial", 0, null, -1, ""); + + test_nsIDOMXULSelectControlElement_init(element, testid); + + // 'appendItem' - check if appendItem works to add a new item + var firstitem = element.appendItem("First Item", "first"); + is(firstitem.localName, childtag, + testid + "appendItem - first item is " + childtag); + test_nsIDOMXULSelectControlElement_States(element, testid + "appendItem", 1, null, -1, ""); + + is(firstitem.control, element, testid + "control"); + + // 'selectedIndex' - check if an item may be selected + element.selectedIndex = 0; + test_nsIDOMXULSelectControlElement_States(element, testid + "selectedIndex", 1, firstitem, 0, firstvalue); + + // 'appendItem 2' - check if a second item may be added + var seconditem = element.appendItem("Second Item", "second"); + test_nsIDOMXULSelectControlElement_States(element, testid + "appendItem 2", 2, firstitem, 0, firstvalue); + + // 'selectedItem' - check if the second item may be selected + element.selectedItem = seconditem; + test_nsIDOMXULSelectControlElement_States(element, testid + "selectedItem", 2, seconditem, 1, secondvalue); + + // 'selectedIndex 2' - check if selectedIndex may be set to -1 to deselect items + var selectionRequired = behaviourContains(element.localName, "selection-required"); + element.selectedIndex = -1; + test_nsIDOMXULSelectControlElement_States(element, testid + "selectedIndex 2", 2, + selectionRequired ? seconditem : null, selectionRequired ? 1 : -1, + selectionRequired ? secondvalue : ""); + + // 'selectedItem 2' - check if the selectedItem property may be set to null + element.selectedIndex = 1; + element.selectedItem = null; + test_nsIDOMXULSelectControlElement_States(element, testid + "selectedItem 2", 2, + selectionRequired ? seconditem : null, selectionRequired ? 1 : -1, + selectionRequired ? secondvalue : ""); + + // 'getIndexOfItem' - check if getIndexOfItem returns the right index + is(element.getIndexOfItem(firstitem), 0, testid + "getIndexOfItem - first item at index 0"); + is(element.getIndexOfItem(seconditem), 1, testid + "getIndexOfItem - second item at index 1"); + + var otheritem = element.ownerDocument.createElement(childtag); + is(element.getIndexOfItem(otheritem), -1, testid + "getIndexOfItem - other item not found"); + + // 'getItemAtIndex' - check if getItemAtIndex returns the right item + is(element.getItemAtIndex(0), firstitem, testid + "getItemAtIndex - index 0 is first item"); + is(element.getItemAtIndex(1), seconditem, testid + "getItemAtIndex - index 0 is second item"); + is(element.getItemAtIndex(-1), null, testid + "getItemAtIndex - index -1 is null"); + is(element.getItemAtIndex(2), null, testid + "getItemAtIndex - index 2 is null"); + + // check if setting the value changes the selection + element.value = firstvalue; + test_nsIDOMXULSelectControlElement_States(element, testid + "set value 1", 2, firstitem, 0, firstvalue); + element.value = secondvalue; + test_nsIDOMXULSelectControlElement_States(element, testid + "set value 2", 2, seconditem, 1, secondvalue); + // setting the value attribute to one not in the list doesn't change the selection. + // The value is only changed for elements which support having a value other than the + // selection. + element.value = "other"; + var allowOtherValue = behaviourContains(element.localName, "allow-other-value"); + var otherValueClearsSelection = behaviourContains(element.localName, "other-value-clears-selection"); + test_nsIDOMXULSelectControlElement_States(element, testid + "set value other", 2, + otherValueClearsSelection ? null : seconditem, + otherValueClearsSelection ? -1 : 1, + allowOtherValue ? "other" : secondvalue); + if (allowOtherValue) + element.value = ""; + + // 'removeItemAt' - check if removeItemAt removes the right item + if (selectionRequired) + element.value = secondvalue; + else + element.selectedIndex = -1; + + var removeditem = element.removeItemAt(0); + is(removeditem, firstitem, testid + "removeItemAt return value"); + test_nsIDOMXULSelectControlElement_States(element, testid + "removeItemAt", 1, + selectionRequired ? seconditem : null, selectionRequired ? 0 : -1, + selectionRequired ? secondvalue : ""); + + is(removeditem.control, undefined, testid + "control not set"); + + var thirditem = element.appendItem("Third Item", "third"); + var fourthitem = element.appendItem("Fourth Item", fourthvalue); + var fifthitem = element.appendItem("Fifth Item", "fifth"); + + // 'removeItemAt 2' - check if removeItemAt removes the selected item and + // adjusts the selection to the next item + element.selectedItem = thirditem; + is(element.removeItemAt(1), thirditem, testid + "removeItemAt 2 return value"); + + // radio buttons don't handle removing quite right due to XBL issues, + // so disable testing some of these remove tests for now - bug 367400 + var isnotradio = (element.localName != "radiogroup"); + // XXXndeakin disable these tests for all widgets for now. They require bug 331513. + isnotradio = false; + if (isnotradio) + test_nsIDOMXULSelectControlElement_States(element, testid + "removeItemAt 2", 3, fourthitem, 1, fourthvalue); + + // 'removeItemAt 3' - check if removeItemAt adjusts the selection + // if an earlier item is removed + element.selectedItem = fourthitem; + element.removeItemAt(0); + test_nsIDOMXULSelectControlElement_States(element, testid + "removeItemAt 3", 2, fourthitem, 0, fourthvalue); + + // 'removeItemAt 4' - check if removeItemAt adjusts the selection if the + // last item is selected and removed + element.selectedItem = fifthitem; + element.removeItemAt(1); + if (isnotradio) + test_nsIDOMXULSelectControlElement_States(element, testid + "removeItemAt 4", 1, fourthitem, 0, fourthvalue); + + // 'removeItemAt 5' - check that removeItemAt doesn't fail when removing invalid items + is(element.removeItemAt(-1), null, testid + "removeItemAt 5 return value"); + if (isnotradio) + test_nsIDOMXULSelectControlElement_States(element, testid + "removeItemAt 5", 1, fourthitem, 0, fourthvalue); + + // 'removeItemAt 6' - check that removeItemAt doesn't fail when removing invalid items + is(element.removeItemAt(1), null, testid + "removeItemAt 6 return value"); + is("item removed", "item removed", testid + "removeItemAt 6"); + if (isnotradio) + test_nsIDOMXULSelectControlElement_States(element, testid + "removeItemAt 6", 1, fourthitem, 0, fourthvalue); + + // 'insertItemAt' - check if insertItemAt inserts items at the right locations + element.selectedIndex = 0; + test_nsIDOMXULSelectControlElement_insertItemAt(element, 0, 0, testid, 5); + test_nsIDOMXULSelectControlElement_insertItemAt(element, 2, 2, testid, 6); + test_nsIDOMXULSelectControlElement_insertItemAt(element, -1, 3, testid, 7); + test_nsIDOMXULSelectControlElement_insertItemAt(element, 6, 4, testid, 8); + + element.selectedIndex = 0; + fourthitem.disabled = true; + element.selectedIndex = 1; + test_nsIDOMXULSelectControlElement_States(element, testid + "selectedIndex disabled", 5, fourthitem, 1, fourthvalue); + + element.selectedIndex = 0; + element.selectedItem = fourthitem; + test_nsIDOMXULSelectControlElement_States(element, testid + "selectedIndex disabled", 5, fourthitem, 1, fourthvalue); + + // 'removeall' - check if all items are removed + while (element.itemCount) + element.removeItemAt(0); + if (isnotradio) + test_nsIDOMXULSelectControlElement_States(element, testid + "remove all", 0, null, -1, + allowOtherValue ? "number8" : ""); +} + +function test_nsIDOMXULSelectControlElement_init(element, testprefix) +{ + // editable menulists use the label as the value + var isEditable = (element.localName == "menulist" && element.editable); + + var id = element.id; + element = document.getElementById(id + "-initwithvalue"); + if (element) { + var seconditem = element.getItemAtIndex(1); + test_nsIDOMXULSelectControlElement_States(element, testprefix + " value initialization", + 3, seconditem, 1, + isEditable ? seconditem.label : seconditem.value); + } + + element = document.getElementById(id + "-initwithselected"); + if (element) { + var thirditem = element.getItemAtIndex(2); + test_nsIDOMXULSelectControlElement_States(element, testprefix + " selected initialization", + 3, thirditem, 2, + isEditable ? thirditem.label : thirditem.value); + } +} + +function test_nsIDOMXULSelectControlElement_States(element, testid, + expectedcount, expecteditem, + expectedindex, expectedvalue) +{ + // need an itemCount property here + var count = element.itemCount; + is(count, expectedcount, testid + " item count"); + is(element.selectedItem, expecteditem, testid + " selectedItem"); + is(element.selectedIndex, expectedindex, testid + " selectedIndex"); + is(element.value, expectedvalue, testid + " value"); + if (element.selectedItem) { + is(element.selectedItem.selected, true, + testid + " selectedItem marked as selected"); + } +} + +function test_nsIDOMXULSelectControlElement_insertItemAt(element, index, expectedindex, testid, number) +{ + var expectedCount = element.itemCount; + var expectedSelItem = element.selectedItem; + var expectedSelIndex = element.selectedIndex; + var expectedSelValue = element.value; + + var newitem = element.insertItemAt(index, "Item " + number, "number" + number); + is(element.getIndexOfItem(newitem), expectedindex, + testid + "insertItemAt " + expectedindex + " - get inserted item"); + expectedCount++; + if (expectedSelIndex >= expectedindex) + expectedSelIndex++; + + test_nsIDOMXULSelectControlElement_States(element, testid + "insertItemAt " + index, + expectedCount, expectedSelItem, + expectedSelIndex, expectedSelValue); + return newitem; +} + +/** test_nsIDOMXULSelectControlElement_UI + * + * Test the UI aspects of an element which implements nsIDOMXULSelectControlElement + * + * Parameters: + * element - element to test + */ +function test_nsIDOMXULSelectControlElement_UI(element, testprefix) +{ + var testid = (testprefix) ? testprefix + " " : ""; + testid += element.localName + " nsIDOMXULSelectControlElement UI "; + + while (element.itemCount) + element.removeItemAt(0); + + var firstitem = element.appendItem("First Item", "first"); + var seconditem = element.appendItem("Second Item", "second"); + + // 'mouse select' - check if clicking an item selects it + synthesizeMouseExpectEvent(firstitem, 2, 2, {}, element, "select", testid + "mouse select"); + test_nsIDOMXULSelectControlElement_States(element, testid + "mouse select", 2, firstitem, 0, "first"); + + synthesizeMouseExpectEvent(seconditem, 2, 2, {}, element, "select", testid + "mouse select 2"); + test_nsIDOMXULSelectControlElement_States(element, testid + "mouse select 2", 2, seconditem, 1, "second"); + + // make sure the element is focused so keyboard navigation will apply + element.selectedIndex = 1; + element.focus(); + + var navLeftRight = behaviourContains(element.localName, "keynav-leftright"); + var backKey = navLeftRight ? "VK_LEFT" : "VK_UP"; + var forwardKey = navLeftRight ? "VK_RIGHT" : "VK_DOWN"; + + // 'key select' - check if keypresses move between items + synthesizeKeyExpectEvent(backKey, {}, element, "select", testid + "key up"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key up", 2, firstitem, 0, "first"); + + var keyWrap = behaviourContains(element.localName, "select-keynav-wraps"); + + var expectedItem = keyWrap ? seconditem : firstitem; + var expectedIndex = keyWrap ? 1 : 0; + var expectedValue = keyWrap ? "second" : "first"; + synthesizeKeyExpectEvent(backKey, {}, keyWrap ? element : null, "select", testid + "key up 2"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key up 2", 2, + expectedItem, expectedIndex, expectedValue); + + element.selectedIndex = 0; + synthesizeKeyExpectEvent(forwardKey, {}, element, "select", testid + "key down"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key down", 2, seconditem, 1, "second"); + + expectedItem = keyWrap ? firstitem : seconditem; + expectedIndex = keyWrap ? 0 : 1; + expectedValue = keyWrap ? "first" : "second"; + synthesizeKeyExpectEvent(forwardKey, {}, keyWrap ? element : null, "select", testid + "key down 2"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key down 2", 2, + expectedItem, expectedIndex, expectedValue); + + var thirditem = element.appendItem("Third Item", "third"); + var fourthitem = element.appendItem("Fourth Item", "fourth"); + if (behaviourContains(element.localName, "select-extended-keynav")) { + var fifthitem = element.appendItem("Fifth Item", "fifth"); + var sixthitem = element.appendItem("Sixth Item", "sixth"); + + synthesizeKeyExpectEvent("VK_END", {}, element, "select", testid + "key end"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key end", 6, sixthitem, 5, "sixth"); + + synthesizeKeyExpectEvent("VK_HOME", {}, element, "select", testid + "key home"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key home", 6, firstitem, 0, "first"); + + synthesizeKeyExpectEvent("VK_PAGE_DOWN", {}, element, "select", testid + "key page down"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key page down", 6, fourthitem, 3, "fourth"); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", {}, element, "select", testid + "key page down to end"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key page down to end", 6, sixthitem, 5, "sixth"); + + synthesizeKeyExpectEvent("VK_PAGE_UP", {}, element, "select", testid + "key page up"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key page up", 6, thirditem, 2, "third"); + synthesizeKeyExpectEvent("VK_PAGE_UP", {}, element, "select", testid + "key page up to start"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key page up to start", 6, firstitem, 0, "first"); + + element.removeItemAt(5); + element.removeItemAt(4); + } + + // now test whether a disabled item works. + element.selectedIndex = 0; + seconditem.disabled = true; + + var dontSelectDisabled = (behaviourContains(element.localName, "dont-select-disabled")); + + // 'mouse select' - check if clicking an item selects it + synthesizeMouseExpectEvent(seconditem, 2, 2, {}, element, + dontSelectDisabled ? "!select" : "select", + testid + "mouse select disabled"); + test_nsIDOMXULSelectControlElement_States(element, testid + "mouse select disabled", 4, + dontSelectDisabled ? firstitem: seconditem, dontSelectDisabled ? 0 : 1, + dontSelectDisabled ? "first" : "second"); + + if (dontSelectDisabled) { + // test whether disabling an item won't allow it to be selected + synthesizeKeyExpectEvent(forwardKey, {}, element, "select", testid + "key down disabled"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key down disabled", 4, thirditem, 2, "third"); + + synthesizeKeyExpectEvent(backKey, {}, element, "select", testid + "key up disabled"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key up disabled", 4, firstitem, 0, "first"); + + element.selectedIndex = 2; + firstitem.disabled = true; + + synthesizeKeyExpectEvent(backKey, {}, keyWrap ? element : null, "select", testid + "key up disabled 2"); + expectedItem = keyWrap ? fourthitem : thirditem; + expectedIndex = keyWrap ? 3 : 2; + expectedValue = keyWrap ? "fourth" : "third"; + test_nsIDOMXULSelectControlElement_States(element, testid + "key up disabled 2", 4, + expectedItem, expectedIndex, expectedValue); + } + else { + // in this case, disabled items should behave the same as non-disabled items. + element.selectedIndex = 0; + synthesizeKeyExpectEvent(forwardKey, {}, element, "select", testid + "key down disabled"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key down disabled", 4, seconditem, 1, "second"); + synthesizeKeyExpectEvent(forwardKey, {}, element, "select", testid + "key down disabled again"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key down disabled again", 4, thirditem, 2, "third"); + + synthesizeKeyExpectEvent(backKey, {}, element, "select", testid + "key up disabled"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key up disabled", 4, seconditem, 1, "second"); + synthesizeKeyExpectEvent(backKey, {}, element, "select", testid + "key up disabled again"); + test_nsIDOMXULSelectControlElement_States(element, testid + "key up disabled again", 4, firstitem, 0, "first"); + } +} diff --git a/toolkit/content/tests/fennec-tile-testapp/application.ini b/toolkit/content/tests/fennec-tile-testapp/application.ini new file mode 100644 index 0000000000..510d481809 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/application.ini @@ -0,0 +1,11 @@ +[App] +Vendor=venderr +Name=tile +Version=1.0 +BuildID=20060101 +Copyright=Copyright (c) 2006 Mark Finkle +ID=xulapp@starkravingfinkle.org + +[Gecko] +MinVersion=1.8 +MaxVersion=2.0 diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/chrome.manifest b/toolkit/content/tests/fennec-tile-testapp/chrome/chrome.manifest new file mode 100644 index 0000000000..118354c81a --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/chrome.manifest @@ -0,0 +1 @@ +content tile file:content/ diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/BrowserView.js b/toolkit/content/tests/fennec-tile-testapp/chrome/content/BrowserView.js new file mode 100644 index 0000000000..c498810df3 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/BrowserView.js @@ -0,0 +1,694 @@ +// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- +/* 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/. */ + +var Ci = Components.interfaces; + +// --- REMOVE --- +var noop = function() {}; +var endl = '\n'; +// -------------- + +function BrowserView(container, visibleRect) { + bindAll(this); + this.init(container, visibleRect); +} + +/** + * A BrowserView maintains state of the viewport (browser, zoom level, + * dimensions) and the visible rectangle into the viewport, for every + * browser it is given (cf setBrowser()). In updates to the viewport state, + * a BrowserView (using its TileManager) renders parts of the page quasi- + * intelligently, with guarantees of having rendered and appended all of the + * visible browser content (aka the "critical rectangle"). + * + * State is characterized in large part by two rectangles (and an implicit third): + * - Viewport: Always rooted at the origin, ie with (left, top) at (0, 0). The + * width and height (right and bottom) of this rectangle are that of the + * current viewport, which corresponds more or less to the transformed + * browser content (scaled by zoom level). + * - Visible: Corresponds to the client's viewing rectangle in viewport + * coordinates. Has (top, left) corresponding to position, and width & height + * corresponding to the clients viewing dimensions. Take note that the top + * and left of the visible rect are per-browser state, but that the width + * and height persist across setBrowser() calls. This is best explained by + * a simple example: user views browser A, pans to position (x0, y0), switches + * to browser B, where she finds herself at position (x1, y1), tilts her + * device so that visible rectangle's width and height change, and switches + * back to browser A. She expects to come back to position (x0, y0), but her + * device remains tilted. + * - Critical (the implicit one): The critical rectangle is the (possibly null) + * intersection of the visible and viewport rectangles. That is, it is that + * region of the viewport which is visible to the user. We care about this + * because it tells us which region must be rendered as soon as it is dirtied. + * The critical rectangle is mostly state that we do not keep in BrowserView + * but that our TileManager maintains. + * + * Example rectangle state configurations: + * + * + * +-------------------------------+ + * |A | + * | | + * | | + * | | + * | +----------------+ | + * | |B,C | | + * | | | | + * | | | | + * | | | | + * | +----------------+ | + * | | + * | | + * | | + * | | + * | | + * +-------------------------------+ + * + * + * A = viewport ; at (0, 0) + * B = visible ; at (x, y) where x > 0, y > 0 + * C = critical ; at (x, y) + * + * + * + * +-------------------------------+ + * |A | + * | | + * | | + * | | + * +----+-----------+ | + * |B .C | | + * | . | | + * | . | | + * | . | | + * +----+-----------+ | + * | | + * | | + * | | + * | | + * | | + * +-------------------------------+ + * + * + * A = viewport ; at (0, 0) + * B = visible ; at (x, y) where x < 0, y > 0 + * C = critical ; at (0, y) + * + * + * Maintaining per-browser state is a little bit of a hack involving attaching + * an object as the obfuscated dynamic JS property of the browser object, that + * hopefully no one but us will touch. See getViewportStateFromBrowser() for + * the property name. + */ +BrowserView.prototype = ( +function() { + + // ----------------------------------------------------------- + // Privates + // + + const kZoomLevelMin = 0.2; + const kZoomLevelMax = 4.0; + const kZoomLevelPrecision = 10000; + + function visibleRectToCriticalRect(visibleRect, browserViewportState) { + return visibleRect.intersect(browserViewportState.viewportRect); + } + + function clampZoomLevel(zl) { + let bounded = Math.min(Math.max(kZoomLevelMin, zl), kZoomLevelMax); + return Math.round(bounded * kZoomLevelPrecision) / kZoomLevelPrecision; + } + + function pageZoomLevel(visibleRect, browserW, browserH) { + return clampZoomLevel(visibleRect.width / browserW); + } + + function seenBrowser(browser) { + return !!(browser.__BrowserView__vps); + } + + function initBrowserState(browser, visibleRect) { + let [browserW, browserH] = getBrowserDimensions(browser); + + let zoomLevel = pageZoomLevel(visibleRect, browserW, browserH); + let viewportRect = (new wsRect(0, 0, browserW, browserH)).scale(zoomLevel, zoomLevel); + + dump('--- initing browser to ---' + endl); + browser.__BrowserView__vps = new BrowserView.BrowserViewportState(viewportRect, + visibleRect.x, + visibleRect.y, + zoomLevel); + dump(browser.__BrowserView__vps.toString() + endl); + dump('--------------------------' + endl); + } + + function getViewportStateFromBrowser(browser) { + return browser.__BrowserView__vps; + } + + function getBrowserDimensions(browser) { + return [browser.scrollWidth, browser.scrollHeight]; + } + + function getContentScrollValues(browser) { + let cwu = getBrowserDOMWindowUtils(browser); + let scrollX = {}; + let scrollY = {}; + cwu.getScrollXY(false, scrollX, scrollY); + + return [scrollX.value, scrollY.value]; + } + + function getBrowserDOMWindowUtils(browser) { + return browser.contentWindow + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + } + + function getNewBatchOperationState() { + return { + viewportSizeChanged: false, + dirtyAll: false + }; + } + + function clampViewportWH(width, height, visibleRect) { + let minW = visibleRect.width; + let minH = visibleRect.height; + return [Math.max(width, minW), Math.max(height, minH)]; + } + + function initContainer(container, visibleRect) { + container.style.width = visibleRect.width + 'px'; + container.style.height = visibleRect.height + 'px'; + container.style.overflow = '-moz-hidden-unscrollable'; + } + + function resizeContainerToViewport(container, viewportRect) { + container.style.width = viewportRect.width + 'px'; + container.style.height = viewportRect.height + 'px'; + } + + // !!! --- RESIZE HACK BEGIN ----- + function simulateMozAfterSizeChange(browser, width, height) { + let ev = document.createElement("MouseEvents"); + ev.initEvent("FakeMozAfterSizeChange", false, false, window, 0, width, height); + browser.dispatchEvent(ev); + } + // !!! --- RESIZE HACK END ------- + + // --- Change of coordinates functions --- // + + + // The following returned object becomes BrowserView.prototype + return { + + // ----------------------------------------------------------- + // Public instance methods + // + + init: function init(container, visibleRect) { + this._batchOps = []; + this._container = container; + this._browserViewportState = null; + this._renderMode = 0; + this._tileManager = new TileManager(this._appendTile, this._removeTile, this); + this.setVisibleRect(visibleRect); + + // !!! --- RESIZE HACK BEGIN ----- + // remove this eventually + this._resizeHack = { + maxSeenW: 0, + maxSeenH: 0 + }; + // !!! --- RESIZE HACK END ------- + }, + + setVisibleRect: function setVisibleRect(r) { + let bvs = this._browserViewportState; + let vr = this._visibleRect; + + if (!vr) + this._visibleRect = vr = r.clone(); + else + vr.copyFrom(r); + + if (bvs) { + bvs.visibleX = vr.left; + bvs.visibleY = vr.top; + + // reclamp minimally to the new visible rect + // this.setViewportDimensions(bvs.viewportRect.right, bvs.viewportRect.bottom); + } else + this._viewportChanged(false, false); + }, + + getVisibleRect: function getVisibleRect() { + return this._visibleRect.clone(); + }, + + getVisibleRectX: function getVisibleRectX() { return this._visibleRect.x; }, + getVisibleRectY: function getVisibleRectY() { return this._visibleRect.y; }, + getVisibleRectWidth: function getVisibleRectWidth() { return this._visibleRect.width; }, + getVisibleRectHeight: function getVisibleRectHeight() { return this._visibleRect.height; }, + + setViewportDimensions: function setViewportDimensions(width, height, causedByZoom) { + let bvs = this._browserViewportState; + let vis = this._visibleRect; + + if (!bvs) + return; + + // [width, height] = clampViewportWH(width, height, vis); + bvs.viewportRect.right = width; + bvs.viewportRect.bottom = height; + + // XXX we might not want the user's page to disappear from under them + // at this point, which could happen if the container gets resized such + // that visible rect becomes entirely outside of viewport rect. might + // be wise to define what UX should be in this case, like a move occurs. + // then again, we could also argue this is the responsibility of the + // caller who would do such a thing... + + this._viewportChanged(true, !!causedByZoom); + }, + + setZoomLevel: function setZoomLevel(zl) { + let bvs = this._browserViewportState; + + if (!bvs) + return; + + let newZL = clampZoomLevel(zl); + + if (newZL != bvs.zoomLevel) { + let browserW = this.viewportToBrowser(bvs.viewportRect.right); + let browserH = this.viewportToBrowser(bvs.viewportRect.bottom); + bvs.zoomLevel = newZL; // side-effect: now scale factor in transformations is newZL + this.setViewportDimensions(this.browserToViewport(browserW), + this.browserToViewport(browserH)); + } + }, + + getZoomLevel: function getZoomLevel() { + let bvs = this._browserViewportState; + if (!bvs) + return undefined; + + return bvs.zoomLevel; + }, + + beginBatchOperation: function beginBatchOperation() { + this._batchOps.push(getNewBatchOperationState()); + this.pauseRendering(); + }, + + commitBatchOperation: function commitBatchOperation() { + let bops = this._batchOps; + + if (bops.length == 0) + return; + + let opState = bops.pop(); + this._viewportChanged(opState.viewportSizeChanged, opState.dirtyAll); + this.resumeRendering(); + }, + + discardBatchOperation: function discardBatchOperation() { + let bops = this._batchOps; + bops.pop(); + this.resumeRendering(); + }, + + discardAllBatchOperations: function discardAllBatchOperations() { + let bops = this._batchOps; + while (bops.length > 0) + this.discardBatchOperation(); + }, + + moveVisibleBy: function moveVisibleBy(dx, dy) { + let vr = this._visibleRect; + let vs = this._browserViewportState; + + this.onBeforeVisibleMove(dx, dy); + this.onAfterVisibleMove(dx, dy); + }, + + moveVisibleTo: function moveVisibleTo(x, y) { + let visibleRect = this._visibleRect; + let dx = x - visibleRect.x; + let dy = y - visibleRect.y; + this.moveBy(dx, dy); + }, + + /** + * Calls to this function need to be one-to-one with calls to + * resumeRendering() + */ + pauseRendering: function pauseRendering() { + this._renderMode++; + }, + + /** + * Calls to this function need to be one-to-one with calls to + * pauseRendering() + */ + resumeRendering: function resumeRendering(renderNow) { + if (this._renderMode > 0) + this._renderMode--; + + if (renderNow || this._renderMode == 0) + this._tileManager.criticalRectPaint(); + }, + + isRendering: function isRendering() { + return (this._renderMode == 0); + }, + + /** + * @param dx Guess delta to destination x coordinate + * @param dy Guess delta to destination y coordinate + */ + onBeforeVisibleMove: function onBeforeVisibleMove(dx, dy) { + let vs = this._browserViewportState; + let vr = this._visibleRect; + + let destCR = visibleRectToCriticalRect(vr.clone().translate(dx, dy), vs); + + this._tileManager.beginCriticalMove(destCR); + }, + + /** + * @param dx Actual delta to destination x coordinate + * @param dy Actual delta to destination y coordinate + */ + onAfterVisibleMove: function onAfterVisibleMove(dx, dy) { + let vs = this._browserViewportState; + let vr = this._visibleRect; + + vr.translate(dx, dy); + vs.visibleX = vr.left; + vs.visibleY = vr.top; + + let cr = visibleRectToCriticalRect(vr, vs); + + this._tileManager.endCriticalMove(cr, this.isRendering()); + }, + + setBrowser: function setBrowser(browser, skipZoom) { + let currentBrowser = this._browser; + + let browserChanged = (currentBrowser !== browser); + + if (currentBrowser) { + currentBrowser.removeEventListener("MozAfterPaint", this.handleMozAfterPaint, false); + + // !!! --- RESIZE HACK BEGIN ----- + // change to the real event type and perhaps refactor the handler function name + currentBrowser.removeEventListener("FakeMozAfterSizeChange", this.handleMozAfterSizeChange, false); + // !!! --- RESIZE HACK END ------- + + this.discardAllBatchOperations(); + + currentBrowser.setAttribute("type", "content"); + currentBrowser.docShell.isOffScreenBrowser = false; + } + + this._restoreBrowser(browser); + + browser.setAttribute("type", "content-primary"); + + this.beginBatchOperation(); + + browser.addEventListener("MozAfterPaint", this.handleMozAfterPaint, false); + + // !!! --- RESIZE HACK BEGIN ----- + // change to the real event type and perhaps refactor the handler function name + browser.addEventListener("FakeMozAfterSizeChange", this.handleMozAfterSizeChange, false); + // !!! --- RESIZE HACK END ------- + + if (!skipZoom) { + browser.docShell.isOffScreenBrowser = true; + this.zoomToPage(); + } + + this._viewportChanged(browserChanged, browserChanged); + + this.commitBatchOperation(); + }, + + handleMozAfterPaint: function handleMozAfterPaint(ev) { + let browser = this._browser; + let tm = this._tileManager; + let vs = this._browserViewportState; + + let [scrollX, scrollY] = getContentScrollValues(browser); + let clientRects = ev.clientRects; + + // !!! --- RESIZE HACK BEGIN ----- + // remove this, cf explanation in loop below + let hack = this._resizeHack; + let hackSizeChanged = false; + // !!! --- RESIZE HACK END ------- + + let rects = []; + // loop backwards to avoid xpconnect penalty for .length + for (let i = clientRects.length - 1; i >= 0; --i) { + let e = clientRects.item(i); + let r = new wsRect(e.left + scrollX, + e.top + scrollY, + e.width, e.height); + + this.browserToViewportRect(r); + r.round(); + + if (r.right < 0 || r.bottom < 0) + continue; + + // !!! --- RESIZE HACK BEGIN ----- + // remove this. this is where we make 'lazy' calculations + // that hint at a browser size change and fake the size change + // event dispach + if (r.right > hack.maxW) { + hack.maxW = rect.right; + hackSizeChanged = true; + } + if (r.bottom > hack.maxH) { + hack.maxH = rect.bottom; + hackSizeChanged = true; + } + // !!! --- RESIZE HACK END ------- + + r.restrictTo(vs.viewportRect); + rects.push(r); + } + + // !!! --- RESIZE HACK BEGIN ----- + // remove this, cf explanation in loop above + if (hackSizeChanged) + simulateMozAfterSizeChange(browser, hack.maxW, hack.maxH); + // !!! --- RESIZE HACK END ------- + + tm.dirtyRects(rects, this.isRendering()); + }, + + handleMozAfterSizeChange: function handleMozAfterPaint(ev) { + // !!! --- RESIZE HACK BEGIN ----- + // get the correct properties off of the event, these are wrong because + // we're using a MouseEvent since it has an X and Y prop of some sort and + // we piggyback on that. + let w = ev.screenX; + let h = ev.screenY; + // !!! --- RESIZE HACK END ------- + + this.setViewportDimensions(w, h); + }, + + zoomToPage: function zoomToPage() { + let browser = this._browser; + + if (!browser) + return; + + let [w, h] = getBrowserDimensions(browser); + this.setZoomLevel(pageZoomLevel(this._visibleRect, w, h)); + }, + + zoom: function zoom(aDirection) { + if (aDirection == 0) + return; + + var zoomDelta = 0.05; // 1/20 + if (aDirection >= 0) + zoomDelta *= -1; + + this.zoomLevel = this._zoomLevel + zoomDelta; + }, + + viewportToBrowser: function viewportToBrowser(x) { + let bvs = this._browserViewportState; + + if (!bvs) + throw "No browser is set"; + + return x / bvs.zoomLevel; + }, + + browserToViewport: function browserToViewport(x) { + let bvs = this._browserViewportState; + + if (!bvs) + throw "No browser is set"; + + return x * bvs.zoomLevel; + }, + + viewportToBrowserRect: function viewportToBrowserRect(rect) { + let f = this.viewportToBrowser(1.0); + return rect.scale(f, f); + }, + + browserToViewportRect: function browserToViewportRect(rect) { + let f = this.browserToViewport(1.0); + return rect.scale(f, f); + }, + + browserToViewportCanvasContext: function browserToViewportCanvasContext(ctx) { + let f = this.browserToViewport(1.0); + ctx.scale(f, f); + }, + + + // ----------------------------------------------------------- + // Private instance methods + // + + _restoreBrowser: function _restoreBrowser(browser) { + let vr = this._visibleRect; + + if (!seenBrowser(browser)) + initBrowserState(browser, vr); + + let bvs = getViewportStateFromBrowser(browser); + + this._contentWindow = browser.contentWindow; + this._browser = browser; + this._browserViewportState = bvs; + vr.left = bvs.visibleX; + vr.top = bvs.visibleY; + this._tileManager.setBrowser(browser); + }, + + _viewportChanged: function _viewportChanged(viewportSizeChanged, dirtyAll) { + let bops = this._batchOps; + + if (bops.length > 0) { + let opState = bops[bops.length - 1]; + + if (viewportSizeChanged) + opState.viewportSizeChanged = viewportSizeChanged; + + if (dirtyAll) + opState.dirtyAll = dirtyAll; + + return; + } + + let bvs = this._browserViewportState; + let vis = this._visibleRect; + + // !!! --- RESIZE HACK BEGIN ----- + // We want to uncomment this for perf, but we can't with the hack in place + // because the mozAfterPaint gives us rects that we use to create the + // fake mozAfterResize event, so we can't just clear things. + /* + if (dirtyAll) { + // We're about to mark the entire viewport dirty, so we can clear any + // queued afterPaint events that will cause redundant draws + getBrowserDOMWindowUtils(this._browser).clearMozAfterPaintEvents(); + } + */ + // !!! --- RESIZE HACK END ------- + + if (bvs) { + resizeContainerToViewport(this._container, bvs.viewportRect); + + this._tileManager.viewportChangeHandler(bvs.viewportRect, + visibleRectToCriticalRect(vis, bvs), + viewportSizeChanged, + dirtyAll); + } + }, + + _appendTile: function _appendTile(tile) { + let canvas = tile.getContentImage(); + + /* + canvas.style.position = "absolute"; + canvas.style.left = tile.x + "px"; + canvas.style.top = tile.y + "px"; + */ + + canvas.setAttribute("style", "position: absolute; left: " + tile.boundRect.left + "px; " + "top: " + tile.boundRect.top + "px;"); + + this._container.appendChild(canvas); + + // dump('++ ' + tile.toString(true) + endl); + }, + + _removeTile: function _removeTile(tile) { + let canvas = tile.getContentImage(); + + this._container.removeChild(canvas); + + // dump('-- ' + tile.toString(true) + endl); + } + + }; + +} +)(); + + +// ----------------------------------------------------------- +// Helper structures +// + +BrowserView.BrowserViewportState = function(viewportRect, + visibleX, + visibleY, + zoomLevel) { + + this.init(viewportRect, visibleX, visibleY, zoomLevel); +}; + +BrowserView.BrowserViewportState.prototype = { + + init: function init(viewportRect, visibleX, visibleY, zoomLevel) { + this.viewportRect = viewportRect; + this.visibleX = visibleX; + this.visibleY = visibleY; + this.zoomLevel = zoomLevel; + }, + + clone: function clone() { + return new BrowserView.BrowserViewportState(this.viewportRect, + this.visibleX, + this.visibleY, + this.zoomLevel); + }, + + toString: function toString() { + let props = ['\tviewportRect=' + this.viewportRect.toString(), + '\tvisibleX=' + this.visibleX, + '\tvisibleY=' + this.visibleY, + '\tzoomLevel=' + this.zoomLevel]; + + return '[BrowserViewportState] {\n' + props.join(',\n') + '\n}'; + } + +}; + diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/FooScript.js b/toolkit/content/tests/fennec-tile-testapp/chrome/content/FooScript.js new file mode 100644 index 0000000000..49cbbed664 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/FooScript.js @@ -0,0 +1,352 @@ +var noop = function() {}; +Browser = { + updateViewportSize: noop + /** *********************************************************** + function + let browser = document.getElementById("googlenews"); + let cdoc = browser.contentDocument; + + // These might not exist yet depending on page load state + var body = cdoc.body || {}; + var html = cdoc.documentElement || {}; + + var w = Math.max(body.scrollWidth || 0, html.scrollWidth); + var h = Math.max(body.scrollHeight || 0, html.scrollHeight); + + window.tileManager.viewportHandler(new wsRect(0, 0, w, h), + window.innerWidth, + new wsRect(0, 0, window.innerWidth, window.innerHeight), + false); + *************************************************************/ +}; +var ws = { + beginUpdateBatch: noop, + panTo: noop, + endUpdateBatch: noop +}; +var Ci = Components.interfaces; +var bv = null; +var endl = "\n"; + + +function BrowserView() { + this.init(); + bindAll(this); +} + +BrowserView.prototype = { + + // --- PROPERTIES --- + // public: + // init() + // getViewportInnerBoundsRect(dx, dy) + // tileManager + // scrollbox + // + // private: + // _scrollbox + // _leftbar + // _rightbar + // _topbar + // _browser + // _tileManager + // _viewportRect + // _viewportInnerBoundsRect + // + + get tileManager() { return this._tileManager; }, + get scrollbox() { return this._scrollbox; }, + + init: function init() { + let scrollbox = document.getElementById("scrollbox").boxObject; + this._scrollbox = scrollbox; + + let leftbar = document.getElementById("left_sidebar"); + let rightbar = document.getElementById("right_sidebar"); + let topbar = document.getElementById("top_urlbar"); + this._leftbar = leftbar; + this._rightbar = rightbar; + this._topbar = topbar; + + scrollbox.scrollTo(Math.round(leftbar.getBoundingClientRect().right), 0); + + let tileContainer = document.getElementById("tile_container"); + tileContainer.addEventListener("mousedown", onMouseDown, true); + tileContainer.addEventListener("mouseup", onMouseUp, true); + tileContainer.addEventListener("mousemove", onMouseMove, true); + this._tileContainer = tileContainer; + + let tileManager = new TileManager(this.appendTile, this.removeTile, window.innerWidth); + this._tileManager = tileManager; + + let browser = document.getElementById("googlenews"); + this.setCurrentBrowser(browser, false); // sets this._browser + + let cdoc = browser.contentDocument; + + // These might not exist yet depending on page load state + let body = cdoc.body || {}; + let html = cdoc.documentElement || {}; + + let w = Math.max(body.scrollWidth || 0, html.scrollWidth); + let h = Math.max(body.scrollHeight || 0, html.scrollHeight); + + let viewportRect = new wsRect(0, 0, w, h); + this._viewportRect = viewportRect; + + let viewportInnerBoundsRect = this.getViewportInnerBoundsRect(); + this._viewportInnerBoundsRect = viewportInnerBoundsRect; + + tileManager.viewportHandler(viewportRect, + window.innerWidth, + viewportInnerBoundsRect, + true); + }, + + resizeTileContainer: function resizeTileContainer() { + + }, + + scrollboxToViewportRect: function scrollboxToViewportRect(rect, clip) { + let leftbar = this._leftbar.getBoundingClientRect(); + let rightbar = this._rightbar.getBoundingClientRect(); + let topbar = this._topbar.getBoundingClientRect(); + + let xtrans = -leftbar.width; + let ytrans = -topbar.height; + let x = rect.x + xtrans; + let y = rect.y + ytrans; + + // XXX we're cheating --- this is not really a clip, but its the only + // way this function is used + rect.x = (clip) ? Math.max(x, 0) : x; + rect.y = (clip) ? Math.max(y, 0) : y; + + return rect; + }, + + getScrollboxPosition: function getScrollboxPosition() { + return [this._scrollbox.positionX, this._scrollbox.positionY]; + }, + + getViewportInnerBoundsRect: function getViewportInnerBoundsRect(dx, dy) { + if (!dx) dx = 0; + if (!dy) dy = 0; + + let w = window.innerWidth; + let h = window.innerHeight; + + let leftbar = this._leftbar.getBoundingClientRect(); + let rightbar = this._rightbar.getBoundingClientRect(); + let topbar = this._topbar.getBoundingClientRect(); + + let leftinner = Math.max(leftbar.right - dx, 0); + let rightinner = Math.min(rightbar.left - dx, w); + let topinner = Math.max(topbar.bottom - dy, 0); + + let [x, y] = this.getScrollboxPosition(); + + return this.scrollboxToViewportRect(new wsRect(x + dx, y + dy, rightinner - leftinner, h - topinner), + true); + }, + + appendTile: function appendTile(tile) { + let canvas = tile.contentImage; + + canvas.style.position = "absolute"; + canvas.style.left = tile.x + "px"; + canvas.style.top = tile.y + "px"; + + let tileContainer = document.getElementById("tile_container"); + tileContainer.appendChild(canvas); + + dump('++ ' + tile.toString() + endl); + }, + + removeTile: function removeTile(tile) { + let canvas = tile.contentImage; + + let tileContainer = document.getElementById("tile_container"); + tileContainer.removeChild(canvas); + + dump('-- ' + tile.toString() + endl); + }, + + scrollBy: function scrollBy(dx, dy) { + // TODO + this.onBeforeScroll(); + this.onAfterScroll(); + }, + + // x: current x + // y: current y + // dx: delta to get to x from current x + // dy: delta to get to y from current y + onBeforeScroll: function onBeforeScroll(x, y, dx, dy) { + this.tileManager.onBeforeScroll(this.getViewportInnerBoundsRect(dx, dy)); + + // shouldn't update margin if it doesn't need to be changed + let sidebars = document.getElementsByClassName("sidebar"); + for (let i = 0; i < sidebars.length; i++) { + let sidebar = sidebars[i]; + sidebar.style.margin = (y + dy) + "px 0px 0px 0px"; + } + + let urlbar = document.getElementById("top_urlbar"); + urlbar.style.margin = "0px 0px 0px " + (x + dx) + "px"; + }, + + onAfterScroll: function onAfterScroll(x, y, dx, dy) { + this.tileManager.onAfterScroll(this.getViewportInnerBoundsRect()); + }, + + setCurrentBrowser: function setCurrentBrowser(browser, skipZoom) { + let currentBrowser = this._browser; + if (currentBrowser) { + // backup state + currentBrowser.mZoomLevel = this.zoomLevel; + currentBrowser.mPanX = ws._viewingRect.x; + currentBrowser.mPanY = ws._viewingRect.y; + + // stop monitor paint events for this browser + currentBrowser.removeEventListener("MozAfterPaint", this.handleMozAfterPaint, false); + currentBrowser.setAttribute("type", "content"); + currentBrowser.docShell.isOffScreenBrowser = false; + } + + browser.setAttribute("type", "content-primary"); + if (!skipZoom) + browser.docShell.isOffScreenBrowser = true; + + // start monitoring paint events for this browser + browser.addEventListener("MozAfterPaint", this.handleMozAfterPaint, false); + + this._browser = browser; + + // endLoading(and startLoading in most cases) calls zoom anyway + if (!skipZoom) { + this.zoomToPage(); + } + + if ("mZoomLevel" in browser) { + // restore last state + ws.beginUpdateBatch(); + ws.panTo(browser.mPanX, browser.mPanY); + this.zoomLevel = browser.mZoomLevel; + ws.endUpdateBatch(true); + + // drop the cache + delete browser.mZoomLevel; + delete browser.mPanX; + delete browser.mPanY; + } + + this.tileManager.browser = browser; + }, + + handleMozAfterPaint: function handleMozAfterPaint(ev) { + this.tileManager.handleMozAfterPaint(ev); + }, + + zoomToPage: function zoomToPage() { + /** ****************************************************** + let needToPanToTop = this._needToPanToTop; + // Ensure pages are panned at the top before zooming/painting + // combine the initial pan + zoom into a transaction + if (needToPanToTop) { + ws.beginUpdateBatch(); + this._needToPanToTop = false; + ws.panTo(0, -BrowserUI.toolbarH); + } + // Adjust the zoomLevel to fit the page contents in our window width + let [contentW, ] = this._contentAreaDimensions; + let fakeW = this._fakeWidth; + + if (contentW > fakeW) + this.zoomLevel = fakeW / contentW; + + if (needToPanToTop) + ws.endUpdateBatch(); + ********************************************************/ + } + +}; + + +function onResize(e) { + let browser = document.getElementById("googlenews"); + let cdoc = browser.contentDocument; + + // These might not exist yet depending on page load state + var body = cdoc.body || {}; + var html = cdoc.documentElement || {}; + + var w = Math.max(body.scrollWidth || 0, html.scrollWidth); + var h = Math.max(body.scrollHeight || 0, html.scrollHeight); + + if (bv) + bv.tileManager.viewportHandler(new wsRect(0, 0, w, h), + window.innerWidth, + bv.getViewportInnerBoundsRect(), + true); +} + +function onMouseDown(e) { + window._isDragging = true; + window._dragStart = {x: e.clientX, y: e.clientY}; + + bv.tileManager.startPanning(); +} + +function onMouseUp() { + window._isDragging = false; + + bv.tileManager.endPanning(); +} + +function onMouseMove(e) { + if (window._isDragging) { + let scrollbox = bv.scrollbox; + + let x = scrollbox.positionX; + let y = scrollbox.positionY; + let w = scrollbox.scrolledWidth; + let h = scrollbox.scrolledHeight; + + let dx = window._dragStart.x - e.clientX; + let dy = window._dragStart.y - e.clientY; + + // XXX if max(x, 0) > scrollwidth we shouldn't do anything (same for y/height) + let newX = Math.max(x + dx, 0); + let newY = Math.max(y + dy, 0); + + if (newX < w || newY < h) { + // clip dx and dy to prevent us from going below 0 + dx = Math.max(dx, -x); + dy = Math.max(dy, -y); + + bv.onBeforeScroll(x, y, dx, dy); + + /* dump("==========scroll==========" + endl); + dump("delta: " + dx + "," + dy + endl); + let xx = {}; + let yy = {}; + scrollbox.getPosition(xx, yy); + dump(xx.value + "," + yy.value + endl);*/ + + scrollbox.scrollBy(dx, dy); + + /* scrollbox.getPosition(xx, yy); + dump(xx.value + "," + yy.value + endl); + dump("==========================" + endl);*/ + + bv.onAfterScroll(); + } + } + + window._dragStart = {x: e.clientX, y: e.clientY}; +} + +function onLoad() { + bv = new BrowserView(); +} diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/TileManager.js b/toolkit/content/tests/fennec-tile-testapp/chrome/content/TileManager.js new file mode 100644 index 0000000000..52beb6e364 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/TileManager.js @@ -0,0 +1,1018 @@ +// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- +/* 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/. */ + +const kXHTMLNamespaceURI = "http://www.w3.org/1999/xhtml"; + +// base-2 exponent for width, height of a single tile. +const kTileExponentWidth = 7; +const kTileExponentHeight = 7; +const kTileWidth = Math.pow(2, kTileExponentWidth); // 2^7 = 128 +const kTileHeight = Math.pow(2, kTileExponentHeight); // 2^7 = 128 +const kLazyRoundTimeCap = 500; // millis + + +function bind(f, thisObj) { + return function() { + return f.apply(thisObj, arguments); + }; +} + +function bindSome(instance, methodNames) { + for (let methodName of methodNames) + if (methodName in instance) + instance[methodName] = bind(instance[methodName], instance); +} + +function bindAll(instance) { + for (let key in instance) + if (instance[key] instanceof Function) + instance[key] = bind(instance[key], instance); +} + + +/** + * The Tile Manager! + * + * @param appendTile The function the tile manager should call in order to + * "display" a tile (e.g. append it to the DOM). The argument to this + * function is a TileManager.Tile object. + * @param removeTile The function the tile manager should call in order to + * "undisplay" a tile (e.g. remove it from the DOM). The argument to this + * function is a TileManager.Tile object. + * @param fakeWidth The width of the widest possible visible rectangle, e.g. + * the width of the screen. This is used in setting the zoomLevel. + */ +function TileManager(appendTile, removeTile, browserView) { + // backref to the BrowserView object that owns us + this._browserView = browserView; + + // callbacks to append / remove a tile to / from the parent + this._appendTile = appendTile; + this._removeTile = removeTile; + + // tile cache holds tile objects and pools them under a given capacity + let self = this; + this._tileCache = new TileManager.TileCache(function(tile) { self._removeTileSafe(tile); }, + -1, -1, 110); + + // Rectangle within the viewport that is visible to the user. It is "critical" + // in the sense that it must be rendered as soon as it becomes dirty + this._criticalRect = null; + + // Current <browser> DOM element, holding the content we wish to render. + // This is null when no browser is attached + this._browser = null; + + // if we have an outstanding paint timeout, its value is stored here + // for cancelling when we end page loads + // this._drawTimeout = 0; + this._pageLoadResizerTimeout = 0; + + // timeout of the non-visible-tiles-crawler to cache renders from the browser + this._idleTileCrawlerTimeout = 0; + + // object that keeps state on our current lazyload crawl + this._crawler = null; + + // the max right coordinate we've seen from paint events + // while we were loading a page. If we see something that's bigger than + // our width, we'll trigger a page zoom. + this._pageLoadMaxRight = 0; + this._pageLoadMaxBottom = 0; + + // Tells us to pan to top before first draw + this._needToPanToTop = false; +} + +TileManager.prototype = { + + setBrowser: function setBrowser(b) { this._browser = b; }, + + // This is the callback fired by our client whenever the viewport + // changed somehow (or didn't change but someone asked it to update). + viewportChangeHandler: function viewportChangeHandler(viewportRect, + criticalRect, + boundsSizeChanged, + dirtyAll) { + // !!! --- DEBUG BEGIN ----- + dump("***vphandler***\n"); + dump(viewportRect.toString() + "\n"); + dump(criticalRect.toString() + "\n"); + dump(boundsSizeChanged + "\n"); + dump(dirtyAll + "\n***************\n"); + // !!! --- DEBUG END ------- + + let tc = this._tileCache; + + tc.iBound = Math.ceil(viewportRect.right / kTileWidth); + tc.jBound = Math.ceil(viewportRect.bottom / kTileHeight); + + if (!criticalRect || !criticalRect.equals(this._criticalRect)) { + this.beginCriticalMove(criticalRect); + this.endCriticalMove(criticalRect, !boundsSizeChanged); + } + + if (boundsSizeChanged) { + // TODO fastpath if !dirtyAll + this.dirtyRects([viewportRect.clone()], true); + } + }, + + dirtyRects: function dirtyRects(rects, doCriticalRender) { + let criticalIsDirty = false; + let criticalRect = this._criticalRect; + + for (let rect of rects) { + this._tileCache.forEachIntersectingRect(rect, false, this._dirtyTile, this); + + if (criticalRect && rect.intersects(criticalRect)) + criticalIsDirty = true; + } + + if (criticalIsDirty && doCriticalRender) + this.criticalRectPaint(); + }, + + criticalRectPaint: function criticalRectPaint() { + let cr = this._criticalRect; + + if (cr) { + let [ctrx, ctry] = cr.centerRounded(); + this.recenterEvictionQueue(ctrx, ctry); + this._renderAppendHoldRect(cr); + } + }, + + beginCriticalMove2: function beginCriticalMove(destCriticalRect) { + let start = Date.now(); + function appendNonDirtyTile(tile) { + if (!tile.isDirty()) + this._appendTileSafe(tile); + } + + if (destCriticalRect) + this._tileCache.forEachIntersectingRect(destCriticalRect, false, appendNonDirtyTile, this); + let end = Date.now(); + dump("start: " + (end-start) + "\n") + }, + + beginCriticalMove: function beginCriticalMove(destCriticalRect) { + /* + function appendNonDirtyTile(tile) { + if (!tile.isDirty()) + this._appendTileSafe(tile); + } + */ + + let start = Date.now(); + + if (destCriticalRect) { + + let rect = destCriticalRect; + + let create = false; + + // this._tileCache.forEachIntersectingRect(destCriticalRect, false, appendNonDirtyTile, this); + let visited = {}; + let evictGuard = null; + if (create) { + evictGuard = function evictGuard(tile) { + return !visited[tile.toString()]; + }; + } + + let starti = rect.left >> kTileExponentWidth; + let endi = rect.right >> kTileExponentWidth; + + let startj = rect.top >> kTileExponentHeight; + let endj = rect.bottom >> kTileExponentHeight; + + let tile = null; + let tc = this._tileCache; + + for (var j = startj; j <= endj; ++j) { + for (var i = starti; i <= endi; ++i) { + + // 'this' for getTile needs to be tc + + // tile = this.getTile(i, j, create, evictGuard); + // if (!tc.inBounds(i, j)) { + if (0 <= i && 0 <= j && i <= tc.iBound && j <= tc.jBound) { + // return null; + break; + } + + tile = null; + + // if (tc._isOccupied(i, j)) { + if (tc._tiles[i] && tc._tiles[i][j]) { + tile = tc._tiles[i][j]; + } else if (create) { + // NOTE: create is false here + tile = tc._createTile(i, j, evictionGuard); + if (tile) tile.markDirty(); + } + + if (tile) { + visited[tile.toString()] = true; + // fn.call(thisObj, tile); + // function appendNonDirtyTile(tile) { + // if (!tile.isDirty()) + if (!tile._dirtyTileCanvas) { + // this._appendTileSafe(tile); + if (!tile._appended) { + let astart = Date.now(); + this._appendTile(tile); + tile._appended = true; + let aend = Date.now(); + dump("append: " + (aend - astart) + "\n"); + } + } + // } + } + } + } + } + + let end = Date.now(); + dump("start: " + (end-start) + "\n") + }, + + endCriticalMove: function endCriticalMove(destCriticalRect, doCriticalPaint) { + let start = Date.now(); + + let tc = this._tileCache; + let cr = this._criticalRect; + + let dcr = destCriticalRect.clone(); + + let f = function releaseOldTile(tile) { + // release old tile + if (!tile.boundRect.intersects(dcr)) + tc.releaseTile(tile); + } + + if (cr) + tc.forEachIntersectingRect(cr, false, f, this); + + this._holdRect(destCriticalRect); + + if (cr) + cr.copyFrom(destCriticalRect); + else + this._criticalRect = cr = destCriticalRect; + + let crpstart = Date.now(); + if (doCriticalPaint) + this.criticalRectPaint(); + dump(" crp: " + (Date.now() - crpstart) + "\n"); + + let end = Date.now(); + dump("end: " + (end - start) + "\n"); + }, + + restartLazyCrawl: function restartLazyCrawl(startRectOrQueue) { + if (!startRectOrQueue || startRectOrQueue instanceof Array) { + this._crawler = new TileManager.CrawlIterator(this._tileCache); + + if (startRectOrQueue) { + let len = startRectOrQueue.length; + for (let k = 0; k < len; ++k) + this._crawler.enqueue(startRectOrQueue[k].i, startRectOrQueue[k].j); + } + } else { + this._crawler = new TileManager.CrawlIterator(this._tileCache, startRectOrQueue); + } + + if (!this._idleTileCrawlerTimeout) + this._idleTileCrawlerTimeout = setTimeout(this._idleTileCrawler, 2000, this); + }, + + stopLazyCrawl: function stopLazyCrawl() { + this._idleTileCrawlerTimeout = 0; + this._crawler = null; + + let cr = this._criticalRect; + if (cr) { + let [ctrx, ctry] = cr.centerRounded(); + this.recenterEvictionQueue(ctrx, ctry); + } + }, + + recenterEvictionQueue: function recenterEvictionQueue(ctrx, ctry) { + let ctri = ctrx >> kTileExponentWidth; + let ctrj = ctry >> kTileExponentHeight; + + function evictFarTiles(a, b) { + let dista = Math.max(Math.abs(a.i - ctri), Math.abs(a.j - ctrj)); + let distb = Math.max(Math.abs(b.i - ctri), Math.abs(b.j - ctrj)); + return dista - distb; + } + + this._tileCache.sortEvictionQueue(evictFarTiles); + }, + + _renderTile: function _renderTile(tile) { + if (tile.isDirty()) + tile.render(this._browser, this._browserView); + }, + + _appendTileSafe: function _appendTileSafe(tile) { + if (!tile._appended) { + this._appendTile(tile); + tile._appended = true; + } + }, + + _removeTileSafe: function _removeTileSafe(tile) { + if (tile._appended) { + this._removeTile(tile); + tile._appended = false; + } + }, + + _dirtyTile: function _dirtyTile(tile) { + if (!this._criticalRect || !tile.boundRect.intersects(this._criticalRect)) + this._removeTileSafe(tile); + + tile.markDirty(); + + if (this._crawler) + this._crawler.enqueue(tile.i, tile.j); + }, + + _holdRect: function _holdRect(rect) { + this._tileCache.holdTilesIntersectingRect(rect); + }, + + _releaseRect: function _releaseRect(rect) { + this._tileCache.releaseTilesIntersectingRect(rect); + }, + + _renderAppendHoldRect: function _renderAppendHoldRect(rect) { + function renderAppendHoldTile(tile) { + if (tile.isDirty()) + this._renderTile(tile); + + this._appendTileSafe(tile); + this._tileCache.holdTile(tile); + } + + this._tileCache.forEachIntersectingRect(rect, true, renderAppendHoldTile, this); + }, + + _idleTileCrawler: function _idleTileCrawler(self) { + if (!self) self = this; + dump('crawl pass.\n'); + let itered = 0, rendered = 0; + + let start = Date.now(); + let comeAgain = true; + + while ((Date.now() - start) <= kLazyRoundTimeCap) { + let tile = self._crawler.next(); + + if (!tile) { + comeAgain = false; + break; + } + + if (tile.isDirty()) { + self._renderTile(tile); + ++rendered; + } + ++itered; + } + + dump('crawl itered:' + itered + ' rendered:' + rendered + '\n'); + + if (comeAgain) { + self._idleTileCrawlerTimeout = setTimeout(self._idleTileCrawler, 2000, self); + } else { + self.stopLazyCrawl(); + dump('crawl end\n'); + } + } + +}; + + +/** + * The tile cache used by the tile manager to hold and index all + * tiles. Also responsible for pooling tiles and maintaining the + * number of tiles under given capacity. + * + * @param onBeforeTileDetach callback set by the TileManager to call before + * we must "detach" a tile from a tileholder due to needing it elsewhere or + * having to discard it on capacity decrease + * @param capacity the initial capacity of the tile cache, i.e. the max number + * of tiles the cache can have allocated + */ +TileManager.TileCache = function TileCache(onBeforeTileDetach, iBound, jBound, capacity) { + if (arguments.length <= 3 || capacity < 0) + capacity = Infinity; + + // We track all pooled tiles in a 2D array (row, column) ordered as + // they "appear on screen". The array is a grid that functions for + // storage of the tiles and as a lookup map. Each array entry is + // a reference to the tile occupying that space ("tileholder"). Entries + // are not unique, so a tile could be referenced by multiple array entries, + // i.e. a tile could "span" many tile placeholders (e.g. if we merge + // neighbouring tiles). + this._tiles = []; + + // holds the same tiles that _tiles holds, but as contiguous array + // elements, for pooling tiles for reuse under finite capacity + this._tilePool = (capacity == Infinity) ? new Array() : new Array(capacity); + + this._capacity = capacity; + this._nTiles = 0; + this._numFree = 0; + + this._onBeforeTileDetach = onBeforeTileDetach; + + this.iBound = iBound; + this.jBound = jBound; +}; + +TileManager.TileCache.prototype = { + + get size() { return this._nTiles; }, + get numFree() { return this._numFree; }, + + // A comparison function that will compare all free tiles as greater + // than all non-free tiles. Useful because, for instance, to shrink + // the tile pool when capacity is lowered, we want to remove all tiles + // at the new cap and beyond, favoring removal of free tiles first. + evictionCmp: function freeTilesLast(a, b) { + if (a.free == b.free) return (a.j == b.j) ? b.i - a.i : b.j - a.j; + return (a.free) ? 1 : -1; + }, + + getCapacity: function getCapacity() { return this._capacity; }, + + setCapacity: function setCapacity(newCap, skipEvictionQueueSort) { + if (newCap < 0) + throw "Cannot set a negative tile cache capacity"; + + if (newCap == Infinity) { + this._capacity = Infinity; + return; + } else if (this._capacity == Infinity) { + // pretend we had a finite capacity all along and proceed normally + this._capacity = this._tilePool.length; + } + + let rem = null; + + if (newCap < this._capacity) { + // This case is obnoxious. We're decreasing our capacity which means + // we may have to get rid of tiles. Depending on our eviction comparator, + // we probably try to get rid free tiles first, but we might have to throw + // out some nonfree ones too. Note that "throwing out" means the cache + // won't keep them, and they'll get GC'ed as soon as all other refholders + // let go of their refs to the tile. + if (!skipEvictionQueueSort) + this.sortEvictionQueue(); + + rem = this._tilePool.splice(newCap, this._tilePool.length); + + } else { + // This case is win. Extend our tile pool array with new empty space. + this._tilePool.push.apply(this._tilePool, new Array(newCap - this._capacity)); + } + + // update state in the case that we threw things out. + let nTilesDeleted = this._nTiles - newCap; + if (nTilesDeleted > 0) { + let nFreeDeleted = 0; + for (let k = 0; k < nTilesDeleted; ++k) { + if (rem[k].free) + nFreeDeleted++; + + this._detachTile(rem[k].i, rem[k].j); + } + + this._nTiles -= nTilesDeleted; + this._numFree -= nFreeDeleted; + } + + this._capacity = newCap; + }, + + _isOccupied: function _isOccupied(i, j) { + return !!(this._tiles[i] && this._tiles[i][j]); + }, + + _detachTile: function _detachTile(i, j) { + let tile = null; + if (this._isOccupied(i, j)) { + tile = this._tiles[i][j]; + + if (this._onBeforeTileDetach) + this._onBeforeTileDetach(tile); + + this.releaseTile(tile); + delete this._tiles[i][j]; + } + return tile; + }, + + _reassignTile: function _reassignTile(tile, i, j) { + this._detachTile(tile.i, tile.j); // detach + tile.init(i, j); // re-init + this._tiles[i][j] = tile; // attach + return tile; + }, + + _evictTile: function _evictTile(evictionGuard) { + let k = this._nTiles - 1; + let pool = this._tilePool; + let victim = null; + + for (; k >= 0; --k) { + if (pool[k].free && + (!evictionGuard || evictionGuard(pool[k]))) + { + victim = pool[k]; + break; + } + } + + return victim; + }, + + _createTile: function _createTile(i, j, evictionGuard) { + if (!this._tiles[i]) + this._tiles[i] = []; + + let tile = null; + + if (this._nTiles < this._capacity) { + // either capacity is infinite, or we still have room to allocate more + tile = new TileManager.Tile(i, j); + this._tiles[i][j] = tile; + this._tilePool[this._nTiles++] = tile; + this._numFree++; + + } else { + // assert: nTiles == capacity + dump("\nevicting\n"); + tile = this._evictTile(evictionGuard); + if (tile) + this._reassignTile(tile, i, j); + } + + return tile; + }, + + inBounds: function inBounds(i, j) { + return 0 <= i && 0 <= j && i <= this.iBound && j <= this.jBound; + }, + + sortEvictionQueue: function sortEvictionQueue(cmp) { + if (!cmp) cmp = this.evictionCmp; + this._tilePool.sort(cmp); + }, + + /** + * Get a tile by its indices + * + * @param i Column + * @param j Row + * @param create Flag true if the tile should be created in case there is no + * tile at (i, j) + * @param reuseCondition Boolean-valued function to restrict conditions under + * which an old tile may be reused for creating this one. This can happen if + * the cache has reached its capacity and must reuse existing tiles in order to + * create this one. The function is given a Tile object as its argument and + * returns true if the tile is OK for reuse. This argument has no effect if the + * create argument is false. + */ + getTile: function getTile(i, j, create, evictionGuard) { + if (!this.inBounds(i, j)) + return null; + + let tile = null; + + if (this._isOccupied(i, j)) { + tile = this._tiles[i][j]; + } else if (create) { + tile = this._createTile(i, j, evictionGuard); + if (tile) tile.markDirty(); + } + + return tile; + }, + + /** + * Look up (possibly creating) a tile from its viewport coordinates. + * + * @param x + * @param y + * @param create Flag true if the tile should be created in case it doesn't + * already exist at the tileholder corresponding to (x, y) + */ + tileFromPoint: function tileFromPoint(x, y, create) { + let i = x >> kTileExponentWidth; + let j = y >> kTileExponentHeight; + + return this.getTile(i, j, create); + }, + + /** + * Hold a tile (i.e. mark it non-free). Returns true if the operation + * actually did something, false elsewise. + */ + holdTile: function holdTile(tile) { + if (tile && tile.free) { + tile._hold(); + this._numFree--; + return true; + } + return false; + }, + + /** + * Release a tile (i.e. mark it free). Returns true if the operation + * actually did something, false elsewise. + */ + releaseTile: function releaseTile(tile) { + if (tile && !tile.free) { + tile._release(); + this._numFree++; + return true; + } + return false; + }, + + // XXX the following two functions will iterate through duplicate tiles + // once we begin to merge tiles. + /** + * Fetch all tiles that share at least one point with this rect. If `create' + * is true then any tileless tileholders will have tiles created for them. + */ + tilesIntersectingRect: function tilesIntersectingRect(rect, create) { + let dx = (rect.right % kTileWidth) - (rect.left % kTileWidth); + let dy = (rect.bottom % kTileHeight) - (rect.top % kTileHeight); + let tiles = []; + + for (let y = rect.top; y <= rect.bottom - dy; y += kTileHeight) { + for (let x = rect.left; x <= rect.right - dx; x += kTileWidth) { + let tile = this.tileFromPoint(x, y, create); + if (tile) + tiles.push(tile); + } + } + + return tiles; + }, + + forEachIntersectingRect: function forEachIntersectingRect(rect, create, fn, thisObj) { + let visited = {}; + let evictGuard = null; + if (create) { + evictGuard = function evictGuard(tile) { + return !visited[tile.toString()]; + }; + } + + let starti = rect.left >> kTileExponentWidth; + let endi = rect.right >> kTileExponentWidth; + + let startj = rect.top >> kTileExponentHeight; + let endj = rect.bottom >> kTileExponentHeight; + + let tile = null; + for (var j = startj; j <= endj; ++j) { + for (var i = starti; i <= endi; ++i) { + tile = this.getTile(i, j, create, evictGuard); + if (tile) { + visited[tile.toString()] = true; + fn.call(thisObj, tile); + } + } + } + }, + + holdTilesIntersectingRect: function holdTilesIntersectingRect(rect) { + this.forEachIntersectingRect(rect, false, this.holdTile, this); + }, + + releaseTilesIntersectingRect: function releaseTilesIntersectingRect(rect) { + this.forEachIntersectingRect(rect, false, this.releaseTile, this); + } + +}; + + + +TileManager.Tile = function Tile(i, j) { + // canvas element is where we keep paint data from browser for this tile + this._canvas = document.createElementNS(kXHTMLNamespaceURI, "canvas"); + this._canvas.setAttribute("width", String(kTileWidth)); + this._canvas.setAttribute("height", String(kTileHeight)); + this._canvas.setAttribute("moz-opaque", "true"); + // this._canvas.style.border = "1px solid red"; + + this.init(i, j); // defines more properties, cf below +}; + +TileManager.Tile.prototype = { + + // essentially, this is part of constructor code, but since we reuse tiles + // in the tile cache, this is here so that we can reinitialize tiles when we + // reuse them + init: function init(i, j) { + if (!this.boundRect) + this.boundRect = new wsRect(i * kTileWidth, j * kTileHeight, kTileWidth, kTileHeight); + else + this.boundRect.setRect(i * kTileWidth, j * kTileHeight, kTileWidth, kTileHeight); + + // indices! + this.i = i; + this.j = j; + + // flags true if we need to repaint our own local canvas + this._dirtyTileCanvas = false; + + // keep a dirty rectangle (i.e. only part of the tile is dirty) + this._dirtyTileCanvasRect = null; + + // flag used by TileManager to avoid re-appending tiles that have already + // been appended + this._appended = false; + + // We keep tile objects around after their use for later reuse, so this + // flags true for an unused pooled tile. We don't actually care about + // this from within the Tile prototype, it is here for the cache to use. + this.free = true; + }, + + // viewport coordinates + get x() { return this.boundRect.left; }, + get y() { return this.boundRect.top; }, + + // the actual canvas that holds the most recently rendered image of this + // canvas + getContentImage: function getContentImage() { return this._canvas; }, + + isDirty: function isDirty() { return this._dirtyTileCanvas; }, + + /** + * Mark this entire tile as dirty (i.e. the whole tile needs to be rendered + * on next render). + */ + markDirty: function markDirty() { this.updateDirtyRegion(); }, + + unmarkDirty: function unmarkDirty() { + this._dirtyTileCanvasRect = null; + this._dirtyTileCanvas = false; + }, + + /** + * This will mark dirty at least everything in dirtyRect (which must be + * specified in canvas coordinates). If dirtyRect is not given then + * the entire tile is marked dirty. + */ + updateDirtyRegion: function updateDirtyRegion(dirtyRect) { + if (!dirtyRect) { + + if (!this._dirtyTileCanvasRect) + this._dirtyTileCanvasRect = this.boundRect.clone(); + else + this._dirtyTileCanvasRect.copyFrom(this.boundRect); + + } else if (!this._dirtyTileCanvasRect) { + this._dirtyTileCanvasRect = dirtyRect.intersect(this.boundRect); + } else if (dirtyRect.intersects(this.boundRect)) { + this._dirtyTileCanvasRect.expandToContain(dirtyRect.intersect(this.boundRect)); + } + + // TODO if after the above, the dirty rectangle is large enough, + // mark the whole tile dirty. + + if (this._dirtyTileCanvasRect) + this._dirtyTileCanvas = true; + }, + + /** + * Actually draw the browser content into the dirty region of this + * tile. This requires us to actually draw with the + * nsIDOMCanvasRenderingContext2D object's drawWindow method, which + * we expect to be a relatively heavy operation. + * + * You likely want to check if the tile isDirty() before asking it + * to render, as this will cause the entire tile to re-render in the + * case that it is not dirty. + */ + render: function render(browser, browserView) { + if (!this.isDirty()) + this.markDirty(); + + let rect = this._dirtyTileCanvasRect; + + let x = rect.left - this.boundRect.left; + let y = rect.top - this.boundRect.top; + + // content process is not being scaled, so don't scale our rect either + // browserView.viewportToBrowserRect(rect); + // rect.round(); // snap outward to get whole "pixel" (in browser coords) + + let ctx = this._canvas.getContext("2d"); + ctx.save(); + + browserView.browserToViewportCanvasContext(ctx); + + ctx.translate(x, y); + + let cw = browserView._contentWindow; + // let cw = browser.contentWindow; + ctx.asyncDrawXULElement(browserView._browser, + rect.left, rect.top, + rect.right - rect.left, rect.bottom - rect.top, + "grey", + (ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_CARET)); + + ctx.restore(); + + this.unmarkDirty(); + }, + + toString: function toString(more) { + if (more) { + return 'Tile(' + [this.i, + this.j, + "dirty=" + this.isDirty(), + "boundRect=" + this.boundRect].join(', ') + + ')'; + } + + return 'Tile(' + this.i + ', ' + this.j + ')'; + }, + + _hold: function hold() { this.free = false; }, + _release: function release() { this.free = true; } + +}; + + +/** + * A CrawlIterator is in charge of creating and returning subsequent tiles "crawled" + * over as we render tiles lazily. It supports iterator semantics so you can use + * CrawlIterator objects in for..in loops. + * + * Currently the CrawlIterator is built to expand a rectangle iteratively and return + * subsequent tiles that intersect the boundary of the rectangle. Each expansion of + * the rectangle is one unit of tile dimensions in each direction. This is repeated + * until all tiles from elsewhere have been reused (assuming the cache has finite + * capacity) in this crawl, so that we don't start reusing tiles from the beginning + * of our crawl. Afterward, the CrawlIterator enters a state where it operates as a + * FIFO queue, and calls to next() simply dequeue elements, which must be added with + * enqueue(). + * + * @param tileCache The TileCache over whose tiles this CrawlIterator will crawl + * @param startRect [optional] The rectangle that we grow in the first (rectangle + * expansion) iteration state. + */ +TileManager.CrawlIterator = function CrawlIterator(tileCache, startRect) { + this._tileCache = tileCache; + this._stepRect = startRect; + + // used to remember tiles that we've reused during this crawl + this._visited = {}; + + // filters the tiles we've already reused once from being considered victims + // for reuse when we ask the tile cache to create a new tile + let visited = this._visited; + this._notVisited = function(tile) { return !visited[tile]; }; + + // a generator that generates tile indices corresponding to tiles intersecting + // the boundary of an expanding rectangle + this._crawlIndices = !startRect ? null : (function indicesGenerator(rect, tc) { + let outOfBounds = false; + while (!outOfBounds) { + // expand rect + rect.left -= kTileWidth; + rect.right += kTileWidth; + rect.top -= kTileHeight; + rect.bottom += kTileHeight; + + let dx = (rect.right % kTileWidth) - (rect.left % kTileWidth); + let dy = (rect.bottom % kTileHeight) - (rect.top % kTileHeight); + + outOfBounds = true; + + // top, bottom borders + for (let y of [rect.top, rect.bottom]) { + for (let x = rect.left; x <= rect.right - dx; x += kTileWidth) { + let i = x >> kTileExponentWidth; + let j = y >> kTileExponentHeight; + if (tc.inBounds(i, j)) { + outOfBounds = false; + yield [i, j]; + } + } + } + + // left, right borders + for (let x of [rect.left, rect.right]) { + for (let y = rect.top; y <= rect.bottom - dy; y += kTileHeight) { + let i = x >> kTileExponentWidth; + let j = y >> kTileExponentHeight; + if (tc.inBounds(i, j)) { + outOfBounds = false; + yield [i, j]; + } + } + } + } + })(this._stepRect, this._tileCache), // instantiate the generator + + // after we finish the rectangle iteration state, we enter the FIFO queue state + this._queueState = !startRect; + this._queue = []; + + // used to prevent tiles from being enqueued twice --- "patience, we'll get to + // it in a moment" + this._enqueued = {}; +}; + +TileManager.CrawlIterator.prototype = { + __iterator__: function*() { + while (true) { + let tile = this.next(); + if (!tile) break; + yield tile; + } + }, + + becomeQueue: function becomeQueue() { + this._queueState = true; + }, + + unbecomeQueue: function unbecomeQueue() { + this._queueState = false; + }, + + next: function next() { + if (this._queueState) + return this.dequeue(); + + let tile = null; + + if (this._crawlIndices) { + try { + let [i, j] = this._crawlIndices.next(); + tile = this._tileCache.getTile(i, j, true, this._notVisited); + } catch (e) { + if (!(e instanceof StopIteration)) + throw e; + } + } + + if (tile) { + this._visited[tile] = true; + } else { + this.becomeQueue(); + return this.next(); + } + + return tile; + }, + + dequeue: function dequeue() { + let tile = null; + do { + let idx = this._queue.shift(); + if (!idx) + return null; + + delete this._enqueued[idx]; + let [i, j] = this._unstrIndices(idx); + tile = this._tileCache.getTile(i, j, false); + + } while (!tile); + + return tile; + }, + + enqueue: function enqueue(i, j) { + let idx = this._strIndices(i, j); + if (!this._enqueued[idx]) { + this._queue.push(idx); + this._enqueued[idx] = true; + } + }, + + _strIndices: function _strIndices(i, j) { + return i + "," + j; + }, + + _unstrIndices: function _unstrIndices(str) { + return str.split(','); + } + +}; diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/WidgetStack.js b/toolkit/content/tests/fennec-tile-testapp/chrome/content/WidgetStack.js new file mode 100644 index 0000000000..69288e725f --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/WidgetStack.js @@ -0,0 +1,1438 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* 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/. */ + +var gWsDoLog = false; +var gWsLogDiv = null; + +function logbase() { + if (!gWsDoLog) + return; + + if (gWsLogDiv == null && "console" in window) { + console.log.apply(console, arguments); + } else { + var s = ""; + for (var i = 0; i < arguments.length; i++) { + s += arguments[i] + " "; + } + s += "\n"; + if (gWsLogDiv) { + gWsLogDiv.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "br")); + gWsLogDiv.appendChild(document.createTextNode(s)); + } + + dump(s); + } +} + +function dumpJSStack(stopAtNamedFunction) { + let caller = Components.stack.caller; + dump("\tStack: " + caller.name); + while ((caller = caller.caller)) { + dump(" <- " + caller.name); + if (stopAtNamedFunction && caller.name != "anonymous") + break; + } + dump("\n"); +} + +function log() { + // logbase.apply(window, arguments); +} + +function log2() { + // logbase.apply(window, arguments); +} + +var reportError = log; + +/* + * wsBorder class + * + * Simple container for top,left,bottom,right "border" values + */ +function wsBorder(t, l, b, r) { + this.setBorder(t, l, b, r); +} + +wsBorder.prototype = { + + setBorder: function (t, l, b, r) { + this.top = t; + this.left = l; + this.bottom = b; + this.right = r; + }, + + toString: function () { + return "[l:" + this.left + ",t:" + this.top + ",r:" + this.right + ",b:" + this.bottom + "]"; + } +}; + +/* + * wsRect class + * + * Rectangle class, with both x/y/w/h and t/l/b/r accessors. + */ +function wsRect(x, y, w, h) { + this.left = x; + this.top = y; + this.right = x+w; + this.bottom = y+h; +} + +wsRect.prototype = { + + get x() { return this.left; }, + get y() { return this.top; }, + get width() { return this.right - this.left; }, + get height() { return this.bottom - this.top; }, + set x(v) { + let diff = this.left - v; + this.left = v; + this.right -= diff; + }, + set y(v) { + let diff = this.top - v; + this.top = v; + this.bottom -= diff; + }, + set width(v) { this.right = this.left + v; }, + set height(v) { this.bottom = this.top + v; }, + + setRect: function(x, y, w, h) { + this.left = x; + this.top = y; + this.right = x+w; + this.bottom = y+h; + + return this; + }, + + setBounds: function(t, l, b, r) { + this.top = t; + this.left = l; + this.bottom = b; + this.right = r; + + return this; + }, + + equals: function equals(r) { + return (r != null && + this.top == r.top && + this.left == r.left && + this.bottom == r.bottom && + this.right == r.right); + }, + + clone: function clone() { + return new wsRect(this.left, this.top, this.right - this.left, this.bottom - this.top); + }, + + center: function center() { + return [this.left + (this.right - this.left) / 2, + this.top + (this.bottom - this.top) / 2]; + }, + + centerRounded: function centerRounded() { + return this.center().map(Math.round); + }, + + copyFrom: function(r) { + this.top = r.top; + this.left = r.left; + this.bottom = r.bottom; + this.right = r.right; + + return this; + }, + + copyFromTLBR: function(r) { + this.left = r.left; + this.top = r.top; + this.right = r.right; + this.bottom = r.bottom; + + return this; + }, + + translate: function(x, y) { + this.left += x; + this.right += x; + this.top += y; + this.bottom += y; + + return this; + }, + + // return a new wsRect that is the union of that one and this one + union: function(rect) { + let l = Math.min(this.left, rect.left); + let r = Math.max(this.right, rect.right); + let t = Math.min(this.top, rect.top); + let b = Math.max(this.bottom, rect.bottom); + + return new wsRect(l, t, r-l, b-t); + }, + + toString: function() { + return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]"; + }, + + expandBy: function(b) { + this.left += b.left; + this.right += b.right; + this.top += b.top; + this.bottom += b.bottom; + return this; + }, + + contains: function(other) { + return !!(other.left >= this.left && + other.right <= this.right && + other.top >= this.top && + other.bottom <= this.bottom); + }, + + intersect: function(r2) { + let xmost1 = this.right; + let xmost2 = r2.right; + + let x = Math.max(this.left, r2.left); + + let temp = Math.min(xmost1, xmost2); + if (temp <= x) + return null; + + let width = temp - x; + + let ymost1 = this.bottom; + let ymost2 = r2.bottom; + let y = Math.max(this.top, r2.top); + + temp = Math.min(ymost1, ymost2); + if (temp <= y) + return null; + + let height = temp - y; + + return new wsRect(x, y, width, height); + }, + + intersects: function(other) { + let xok = (other.left > this.left && other.left < this.right) || + (other.right > this.left && other.right < this.right) || + (other.left <= this.left && other.right >= this.right); + let yok = (other.top > this.top && other.top < this.bottom) || + (other.bottom > this.top && other.bottom < this.bottom) || + (other.top <= this.top && other.bottom >= this.bottom); + return xok && yok; + }, + + /** + * Similar to (and most code stolen from) intersect(). A restriction + * is an intersection, but this modifies the receiving object instead + * of returning a new rect. + */ + restrictTo: function restrictTo(r2) { + let xmost1 = this.right; + let xmost2 = r2.right; + + let x = Math.max(this.left, r2.left); + + let temp = Math.min(xmost1, xmost2); + if (temp <= x) + throw "Intersection is empty but rects cannot be empty"; + + let width = temp - x; + + let ymost1 = this.bottom; + let ymost2 = r2.bottom; + let y = Math.max(this.top, r2.top); + + temp = Math.min(ymost1, ymost2); + if (temp <= y) + throw "Intersection is empty but rects cannot be empty"; + + let height = temp - y; + + return this.setRect(x, y, width, height); + }, + + /** + * Similar to (and most code stolen from) union(). An extension is a + * union (in our sense of the term, not the common set-theoretic sense), + * but this modifies the receiving object instead of returning a new rect. + * Effectively, this rectangle is expanded minimally to contain all of the + * other rect. "Expanded minimally" means that the rect may shrink if + * given a strict subset rect as the argument. + */ + expandToContain: function extendTo(rect) { + let l = Math.min(this.left, rect.left); + let r = Math.max(this.right, rect.right); + let t = Math.min(this.top, rect.top); + let b = Math.max(this.bottom, rect.bottom); + + return this.setRect(l, t, r-l, b-t); + }, + + round: function round(scale) { + if (!scale) scale = 1; + + this.left = Math.floor(this.left * scale) / scale; + this.top = Math.floor(this.top * scale) / scale; + this.right = Math.ceil(this.right * scale) / scale; + this.bottom = Math.ceil(this.bottom * scale) / scale; + + return this; + }, + + scale: function scale(xscl, yscl) { + this.left *= xscl; + this.right *= xscl; + this.top *= yscl; + this.bottom *= yscl; + + return this; + } +}; + +/* + * The "Widget Stack" + * + * Manages a <xul:stack>'s children, allowing them to be dragged around + * the stack, subject to specified constraints. Optionally supports + * one widget designated as the viewport, which can be panned over a virtual + * area without needing to draw that area entirely. The viewport widget + * is designated by a 'viewport' attribute on the child element. + * + * Widgets are subject to various constraints, specified in xul via the + * 'constraint' attribute. Current constraints are: + * ignore-x: When panning, ignore any changes to the widget's x position + * ignore-y: When panning, ignore any changes to the widget's y position + * vp-relative: This widget's position should be claculated relative to + * the viewport widget. It will always keep the same offset from that + * widget as initially laid out, regardless of changes to the viewport + * bounds. + * frozen: This widget is in a fixed position and should never pan. + */ +function WidgetStack(el, ew, eh) { + this.init(el, ew, eh); +} + +WidgetStack.prototype = { + // the <stack> element + _el: null, + + // object indexed by widget id, with state struct for each object (see _addNewWidget) + _widgetState: null, + + // any barriers + _barriers: null, + + // If a viewport widget is present, this will point to its state object; + // otherwise null. + _viewport: null, + + // a wsRect; the inner bounds of the viewport content + _viewportBounds: null, + // a wsBorder; the overflow area to the side of the bounds where our + // viewport-relative widgets go + _viewportOverflow: null, + + // a wsRect; the viewportBounds expanded by the viewportOverflow + _pannableBounds: null, + get pannableBounds() { + if (!this._pannableBounds) { + this._pannableBounds = this._viewportBounds.clone() + .expandBy(this._viewportOverflow); + } + return this._pannableBounds.clone(); + }, + + // a wsRect; the currently visible part of pannableBounds. + _viewingRect: null, + + // the amount of current global offset applied to all widgets (whether + // static or not). Set via offsetAll(). Can be used to push things + // out of the way for overlaying some other UI. + globalOffsetX: 0, + globalOffsetY: 0, + + // if true (default), panning is constrained to the pannable bounds. + _constrainToViewport: true, + + _viewportUpdateInterval: -1, + _viewportUpdateTimeout: -1, + + _viewportUpdateHandler: null, + _panHandler: null, + + _dragState: null, + + _skipViewportUpdates: 0, + _forceViewportUpdate: false, + + // + // init: + // el: the <stack> element whose children are to be managed + // + init: function (el, ew, eh) { + this._el = el; + this._widgetState = {}; + this._barriers = []; + + let rect = this._el.getBoundingClientRect(); + let width = rect.width; + let height = rect.height; + + if (ew != undefined && eh != undefined) { + width = ew; + height = eh; + } + + this._viewportOverflow = new wsBorder(0, 0, 0, 0); + + this._viewingRect = new wsRect(0, 0, width, height); + + // listen for DOMNodeInserted/DOMNodeRemoved/DOMAttrModified + let children = this._el.childNodes; + for (let i = 0; i < children.length; i++) { + let c = this._el.childNodes[i]; + if (c.tagName == "spacer") + this._addNewBarrierFromSpacer(c); + else + this._addNewWidget(c); + } + + // this also updates the viewportOverflow and pannableBounds + this._updateWidgets(); + + if (this._viewport) { + this._viewportBounds = new wsRect(0, 0, this._viewport.rect.width, this._viewport.rect.height); + } else { + this._viewportBounds = new wsRect(0, 0, 0, 0); + } + }, + + // moveWidgetBy: move the widget with the given id by x,y. Should + // not be used on vp-relative or otherwise frozen widgets (using it + // on the x coordinate for x-ignore widgets and similarily for y is + // ok, as long as the other coordinate remains 0.) + moveWidgetBy: function (wid, x, y) { + let state = this._getState(wid); + + state.rect.x += x; + state.rect.y += y; + + this._commitState(state); + }, + + // panBy: pan the entire set of widgets by the given x and y amounts. + // This does the same thing as if the user dragged by the given amount. + // If this is called with an outstanding drag, weirdness might happen, + // but it also might work, so not disabling that. + // + // if ignoreBarriers is true, then barriers are ignored for the pan. + panBy: function panBy(dx, dy, ignoreBarriers) { + dx = Math.round(dx); + dy = Math.round(dy); + + if (dx == 0 && dy == 0) + return false; + + let needsDragWrap = !this._dragging; + + if (needsDragWrap) + this.dragStart(0, 0); + + let panned = this._panBy(dx, dy, ignoreBarriers); + + if (needsDragWrap) + this.dragStop(); + + return panned; + }, + + // panTo: pan the entire set of widgets so that the given x,y + // coordinates are in the upper left of the stack. If either is + // null or undefined, only move the other axis + panTo: function panTo(x, y) { + if (x == undefined || x == null) + x = this._viewingRect.x; + if (y == undefined || y == null) + y = this._viewingRect.y; + this.panBy(x - this._viewingRect.x, y - this._viewingRect.y, true); + }, + + // freeze: set a widget as frozen. A frozen widget won't be moved + // in the stack -- its x,y position will still be tracked in the + // state, but the left/top attributes won't be overwritten. Call unfreeze + // to move the widget back to where the ws thinks it should be. + freeze: function (wid) { + let state = this._getState(wid); + + state.frozen = true; + }, + + unfreeze: function (wid) { + let state = this._getState(wid); + if (!state.frozen) + return; + + state.frozen = false; + this._commitState(state); + }, + + // moveFrozenTo: move a frozen widget with id wid to x, y in the stack. + // can only be used on frozen widgets + moveFrozenTo: function (wid, x, y) { + let state = this._getState(wid); + if (!state.frozen) + throw "moveFrozenTo on non-frozen widget " + wid; + + state.widget.setAttribute("left", x); + state.widget.setAttribute("top", y); + }, + + // moveUnfrozenTo: move an unfrozen, pannable widget with id wid to x, y in + // the stack. should only be used on unfrozen widgets when a dynamic change + // in position needs to be made. we basically remove, adjust and re-add + // the widget + moveUnfrozenTo: function (wid, x, y) { + delete this._widgetState[wid]; + let widget = document.getElementById(wid); + if (x) widget.setAttribute("left", x); + if (y) widget.setAttribute("top", y); + this._addNewWidget(widget); + this._updateWidgets(); + }, + + // we're relying on viewportBounds and viewingRect having the same origin + get viewportVisibleRect () { + let rect = this._viewportBounds.intersect(this._viewingRect); + if (!rect) + rect = new wsRect(0, 0, 0, 0); + return rect; + }, + + isWidgetFrozen: function isWidgetFrozen(wid) { + return this._getState(wid).frozen; + }, + + // isWidgetVisible: return true if any portion of widget with id wid is + // visible; otherwise return false. + isWidgetVisible: function (wid) { + let state = this._getState(wid); + let visibleStackRect = new wsRect(0, 0, this._viewingRect.width, this._viewingRect.height); + + return visibleStackRect.intersects(state.rect); + }, + + // getWidgetVisibility: returns the percentage that the widget is visible + getWidgetVisibility: function (wid) { + let state = this._getState(wid); + let visibleStackRect = new wsRect(0, 0, this._viewingRect.width, this._viewingRect.height); + + let visibleRect = visibleStackRect.intersect(state.rect); + if (visibleRect) + return [visibleRect.width / state.rect.width, visibleRect.height / state.rect.height] + + return [0, 0]; + }, + + // offsetAll: add an offset to all widgets + offsetAll: function (x, y) { + this.globalOffsetX += x; + this.globalOffsetY += y; + + for (let wid in this._widgetState) { + let state = this._widgetState[wid]; + state.rect.x += x; + state.rect.y += y; + + this._commitState(state); + } + }, + + // setViewportBounds + // nb: an object containing top, left, bottom, right properties + // OR + // width, height: integer values; origin assumed to be 0,0 + // OR + // top, left, bottom, right: integer values + // + // Set the bounds of the viewport area; that is, set the size of the + // actual content that the viewport widget will be providing a view + // over. For example, in the case of a 100x100 viewport showing a + // view into a 100x500 webpage, the viewport bounds would be + // { top: 0, left: 0, bottom: 500, right: 100 }. + // + // setViewportBounds will move all the viewport-relative widgets into + // place based on the new viewport bounds. + setViewportBounds: function setViewportBounds() { + let oldBounds = this._viewportBounds.clone(); + + if (arguments.length == 1) { + this._viewportBounds.copyFromTLBR(arguments[0]); + } else if (arguments.length == 2) { + this._viewportBounds.setRect(0, 0, arguments[0], arguments[1]); + } else if (arguments.length == 4) { + this._viewportBounds.setBounds(arguments[0], + arguments[1], + arguments[2], + arguments[3]); + } else { + throw "Invalid number of arguments to setViewportBounds"; + } + + let vp = this._viewport; + + let dleft = this._viewportBounds.left - oldBounds.left; + let dright = this._viewportBounds.right - oldBounds.right; + let dtop = this._viewportBounds.top - oldBounds.top; + let dbottom = this._viewportBounds.bottom - oldBounds.bottom; + + // log2("setViewportBounds dltrb", dleft, dtop, dright, dbottom); + + // move all vp-relative widgets to be the right offset from the bounds again + for (let wid in this._widgetState) { + let state = this._widgetState[wid]; + if (state.vpRelative) { + // log2("vpRelative widget", state.id, state.rect.x, dleft, dright); + if (state.vpOffsetXBefore) { + state.rect.x += dleft; + } else { + state.rect.x += dright; + } + + if (state.vpOffsetYBefore) { + state.rect.y += dtop; + } else { + state.rect.y += dbottom; + } + + // log2("vpRelative widget", state.id, state.rect.x, dleft, dright); + this._commitState(state); + } + } + + for (let bid in this._barriers) { + let barrier = this._barriers[bid]; + + // log2("setViewportBounds: looking at barrier", bid, barrier.vpRelative, barrier.type); + + if (barrier.vpRelative) { + if (barrier.type == "vertical") { + let q = "v barrier moving from " + barrier.x + " to "; + if (barrier.vpOffsetXBefore) { + barrier.x += dleft; + } else { + barrier.x += dright; + } + // log2(q += barrier.x); + } else if (barrier.type == "horizontal") { + let q = "h barrier moving from " + barrier.y + " to "; + if (barrier.vpOffsetYBefore) { + barrier.y += dtop; + } else { + barrier.y += dbottom; + } + // log2(q += barrier.y); + } + } + } + + // clear the pannable bounds cache to make sure it gets rebuilt + this._pannableBounds = null; + + // now let's make sure that the viewing rect and inner bounds are still valid + this._adjustViewingRect(); + + this._viewportUpdate(0, 0, true); + }, + + // setViewportHandler + // uh: A function object + // + // The given function object is called at the end of every drag and viewport + // bounds change, passing in the new rect that's to be displayed in the + // viewport. + // + setViewportHandler: function (uh) { + this._viewportUpdateHandler = uh; + }, + + // setPanHandler + // uh: A function object + // + // The given functin object is called whenever elements pan; it provides + // the new area of the pannable bounds that's visible in the stack. + setPanHandler: function (uh) { + this._panHandler = uh; + }, + + // dragStart: start a drag, with the current coordinates being clientX,clientY + dragStart: function dragStart(clientX, clientY) { + log("(dragStart)", clientX, clientY); + + if (this._dragState) { + reportError("dragStart with drag already in progress? what?"); + this._dragState = null; + } + + this._dragState = { }; + + let t = Date.now(); + + this._dragState.barrierState = []; + + this._dragState.startTime = t; + // outer x, that is outer from the viewport coordinates. In stack-relative coords. + this._dragState.outerStartX = clientX; + this._dragState.outerStartY = clientY; + + this._dragCoordsFromClient(clientX, clientY, t); + + this._dragState.outerLastUpdateDX = 0; + this._dragState.outerLastUpdateDY = 0; + + if (this._viewport) { + // create a copy of these so that we can compute + // deltas correctly to update the viewport + this._viewport.dragStartRect = this._viewport.rect.clone(); + } + + this._dragState.dragging = true; + }, + + _viewportDragUpdate: function viewportDragUpdate() { + let vws = this._viewport; + this._viewportUpdate((vws.dragStartRect.x - vws.rect.x), + (vws.dragStartRect.y - vws.rect.y)); + }, + + // dragStop: stop any drag in progress + dragStop: function dragStop() { + log("(dragStop)"); + + if (!this._dragging) + return; + + if (this._viewportUpdateTimeout != -1) + clearTimeout(this._viewportUpdateTimeout); + + this._viewportDragUpdate(); + + this._dragState = null; + }, + + // dragMove: process a mouse move to clientX,clientY for an ongoing drag + dragMove: function dragMove(clientX, clientY) { + if (!this._dragging) + return false; + + this._dragCoordsFromClient(clientX, clientY); + + let panned = this._dragUpdate(); + + if (this._viewportUpdateInterval != -1) { + if (this._viewportUpdateTimeout != -1) + clearTimeout(this._viewportUpdateTimeout); + let self = this; + this._viewportUpdateTimeout = setTimeout(function () { self._viewportDragUpdate(); }, this._viewportUpdateInterval); + } + + return panned; + }, + + // dragBy: process a mouse move by dx,dy for an ongoing drag + dragBy: function dragBy(dx, dy) { + return this.dragMove(this._dragState.outerCurX + dx, this._dragState.outerCurY + dy); + }, + + // updateSize: tell the WidgetStack to update its size, because it + // was either resized or some other event took place. + updateSize: function updateSize(width, height) { + if (width == undefined || height == undefined) { + let rect = this._el.getBoundingClientRect(); + width = rect.width; + height = rect.height; + } + + // update widget rects and viewportOverflow, since the resize might have + // caused them to change (widgets first, since the viewportOverflow depends + // on them). + + // XXX these methods aren't working correctly yet, but they aren't strictly + // necessary in Fennec's default config + // for (let wid in this._widgetState) { + // let s = this._widgetState[wid]; + // this._updateWidgetRect(s); + // } + // this._updateViewportOverflow(); + + this._viewingRect.width = width; + this._viewingRect.height = height; + + // Wrap this call in a batch to ensure that we always call the + // viewportUpdateHandler, even if _adjustViewingRect doesn't trigger a pan. + // If it does, the batch also ensures that we don't call the handler twice. + this.beginUpdateBatch(); + this._adjustViewingRect(); + this.endUpdateBatch(); + }, + + beginUpdateBatch: function startUpdate() { + if (!this._skipViewportUpdates) { + this._startViewportBoundsString = this._viewportBounds.toString(); + this._forceViewportUpdate = false; + } + this._skipViewportUpdates++; + }, + + endUpdateBatch: function endUpdate(aForceRedraw) { + if (!this._skipViewportUpdates) + throw new Error("Unbalanced call to endUpdateBatch"); + + this._forceViewportUpdate = this._forceViewportUpdate || aForceRedraw; + + this._skipViewportUpdates--; + if (this._skipViewportUpdates) + return; + + let boundsSizeChanged = + this._startViewportBoundsString != this._viewportBounds.toString(); + this._callViewportUpdateHandler(boundsSizeChanged || this._forceViewportUpdate); + }, + + // + // Internal code + // + + _updateWidgetRect: function(state) { + // don't need to support updating the viewport rect at the moment + // (we'd need to duplicate the vptarget* code from _addNewWidget if we did) + if (state == this._viewport) + return; + + let w = state.widget; + let x = w.getAttribute("left") || 0; + let y = w.getAttribute("top") || 0; + let rect = w.getBoundingClientRect(); + state.rect = new wsRect(parseInt(x), parseInt(y), + rect.right - rect.left, + rect.bottom - rect.top); + if (w.hasAttribute("widgetwidth") && w.hasAttribute("widgetheight")) { + state.rect.width = parseInt(w.getAttribute("widgetwidth")); + state.rect.height = parseInt(w.getAttribute("widgetheight")); + } + }, + + _dumpRects: function () { + dump("WidgetStack:\n"); + dump("\tthis._viewportBounds: " + this._viewportBounds + "\n"); + dump("\tthis._viewingRect: " + this._viewingRect + "\n"); + dump("\tthis._viewport.viewportInnerBounds: " + this._viewport.viewportInnerBounds + "\n"); + dump("\tthis._viewport.rect: " + this._viewport.rect + "\n"); + dump("\tthis._viewportOverflow: " + this._viewportOverflow + "\n"); + dump("\tthis.pannableBounds: " + this.pannableBounds + "\n"); + }, + + // Ensures that _viewingRect is within _pannableBounds (call this when either + // one is resized) + _adjustViewingRect: function _adjustViewingRect() { + let vr = this._viewingRect; + let pb = this.pannableBounds; + + if (pb.contains(vr)) + return; // nothing to do here + + // don't bother adjusting _viewingRect if it can't fit into + // _pannableBounds + if (vr.height > pb.height || vr.width > pb.width) + return; + + let panX = 0, panY = 0; + if (vr.right > pb.right) + panX = pb.right - vr.right; + else if (vr.left < pb.left) + panX = pb.left - vr.left; + + if (vr.bottom > pb.bottom) + panY = pb.bottom - vr.bottom; + else if (vr.top < pb.top) + panY = pb.top - vr.top; + + this.panBy(panX, panY, true); + }, + + _getState: function (wid) { + let w = this._widgetState[wid]; + if (!w) + throw "Unknown widget id '" + wid + "'; widget not in stack"; + return w; + }, + + get _dragging() { + return this._dragState && this._dragState.dragging; + }, + + _viewportUpdate: function _viewportUpdate(dX, dY, boundsChanged) { + if (!this._viewport) + return; + + this._viewportUpdateTimeout = -1; + + let vws = this._viewport; + let vwib = vws.viewportInnerBounds; + let vpb = this._viewportBounds; + + // recover the amount the inner bounds moved by the amount the viewport + // widget moved, but don't include offsets that we're making up from previous + // drags that didn't affect viewportInnerBounds + let [ignoreX, ignoreY] = this._offsets || [0, 0]; + let rx = dX - ignoreX; + let ry = dY - ignoreY; + + [dX, dY] = this._rectTranslateConstrain(rx, ry, vwib, vpb); + + // record the offsets that correspond to the amount of the drag we're ignoring + // to ensure the viewportInnerBounds remains within the viewportBounds + this._offsets = [dX - rx, dY - ry]; + + // adjust the viewportInnerBounds, and snap the viewport back + vwib.translate(dX, dY); + vws.rect.translate(dX, dY); + this._commitState(vws); + + // update this so that we can call this function again during the same drag + // and get the right values. + vws.dragStartRect = vws.rect.clone(); + + this._callViewportUpdateHandler(boundsChanged); + }, + + _callViewportUpdateHandler: function _callViewportUpdateHandler(boundsChanged) { + if (!this._viewport || !this._viewportUpdateHandler || this._skipViewportUpdates) + return; + + let vwb = this._viewportBounds.clone(); + + let vwib = this._viewport.viewportInnerBounds.clone(); + + let vis = this.viewportVisibleRect; + + vwib.left += this._viewport.offsetLeft; + vwib.top += this._viewport.offsetTop; + vwib.right += this._viewport.offsetRight; + vwib.bottom += this._viewport.offsetBottom; + + this._viewportUpdateHandler.apply(window, [vwb, vwib, vis, boundsChanged]); + }, + + _dragCoordsFromClient: function (cx, cy, t) { + this._dragState.curTime = t ? t : Date.now(); + this._dragState.outerCurX = cx; + this._dragState.outerCurY = cy; + + let dx = this._dragState.outerCurX - this._dragState.outerStartX; + let dy = this._dragState.outerCurY - this._dragState.outerStartY; + this._dragState.outerDX = dx; + this._dragState.outerDY = dy; + }, + + _panHandleBarriers: function (dx, dy) { + // XXX unless the barriers are sorted by position, this will break + // with multiple barriers that are near enough to eachother that a + // drag could cross more than one. + + let vr = this._viewingRect; + + // XXX this just stops at the first horizontal and vertical barrier it finds + + // barrier_[xy] is the barrier that was used to get to the final + // barrier_d[xy] value. if null, no barrier, and dx/dy shouldn't + // be replaced with barrier_d[xy]. + let barrier_y = null, barrier_x = null; + let barrier_dy = 0, barrier_dx = 0; + + for (let i = 0; i < this._barriers.length; i++) { + let b = this._barriers[i]; + + // log2("barrier", i, b.type, b.x, b.y); + + if (dx != 0 && b.type == "vertical") { + if (barrier_x != null) { + delete this._dragState.barrierState[i]; + continue; + } + + let alreadyKnownDistance = this._dragState.barrierState[i] || 0; + + // log2("alreadyKnownDistance", alreadyKnownDistance); + + let dbx = 0; + + // 100 <= 100 && 100-(-5) > 100 + + if ((vr.left <= b.x && vr.left+dx > b.x) || + (vr.left >= b.x && vr.left+dx < b.x)) + { + dbx = b.x - vr.left; + } else if ((vr.right <= b.x && vr.right+dx > b.x) || + (vr.right >= b.x && vr.right+dx < b.x)) + { + dbx = b.x - vr.right; + } else { + delete this._dragState.barrierState[i]; + continue; + } + + let leftoverDistance = dbx - dx; + + // log2("initial dbx", dbx, leftoverDistance); + + let dist = Math.abs(leftoverDistance + alreadyKnownDistance) - b.size; + + if (dist >= 0) { + if (dx < 0) + dbx -= dist; + else + dbx += dist; + delete this._dragState.barrierState[i]; + } else { + dbx = 0; + this._dragState.barrierState[i] = leftoverDistance + alreadyKnownDistance; + } + + // log2("final dbx", dbx, "state", this._dragState.barrierState[i]); + + if (Math.abs(barrier_dx) <= Math.abs(dbx)) { + barrier_x = b; + barrier_dx = dbx; + + // log2("new barrier_dx", barrier_dx); + } + } + + if (dy != 0 && b.type == "horizontal") { + if (barrier_y != null) { + delete this._dragState.barrierState[i]; + continue; + } + + let alreadyKnownDistance = this._dragState.barrierState[i] || 0; + + // log2("alreadyKnownDistance", alreadyKnownDistance); + + let dby = 0; + + // 100 <= 100 && 100-(-5) > 100 + + if ((vr.top <= b.y && vr.top+dy > b.y) || + (vr.top >= b.y && vr.top+dy < b.y)) + { + dby = b.y - vr.top; + } else if ((vr.bottom <= b.y && vr.bottom+dy > b.y) || + (vr.bottom >= b.y && vr.bottom+dy < b.y)) + { + dby = b.y - vr.bottom; + } else { + delete this._dragState.barrierState[i]; + continue; + } + + let leftoverDistance = dby - dy; + + // log2("initial dby", dby, leftoverDistance); + + let dist = Math.abs(leftoverDistance + alreadyKnownDistance) - b.size; + + if (dist >= 0) { + if (dy < 0) + dby -= dist; + else + dby += dist; + delete this._dragState.barrierState[i]; + } else { + dby = 0; + this._dragState.barrierState[i] = leftoverDistance + alreadyKnownDistance; + } + + // log2("final dby", dby, "state", this._dragState.barrierState[i]); + + if (Math.abs(barrier_dy) <= Math.abs(dby)) { + barrier_y = b; + barrier_dy = dby; + + // log2("new barrier_dy", barrier_dy); + } + } + } + + if (barrier_x) { + // log2("did barrier_x", barrier_x, "barrier_dx", barrier_dx); + dx = barrier_dx; + } + + if (barrier_y) { + dy = barrier_dy; + } + + return [dx, dy]; + }, + + _panBy: function _panBy(dx, dy, ignoreBarriers) { + let vr = this._viewingRect; + + // check if any barriers would be crossed by this pan, and take them + // into account. do this first. + if (!ignoreBarriers) + [dx, dy] = this._panHandleBarriers(dx, dy); + + // constrain the full drag of the viewingRect to the pannableBounds. + // note that the viewingRect needs to move in the opposite + // direction of the pan, so we fiddle with the signs here (as you + // pan to the upper left, more of the bottom right becomes visible, + // so the viewing rect moves to the bottom right of the virtual surface). + [dx, dy] = this._rectTranslateConstrain(dx, dy, vr, this.pannableBounds); + + // If the net result is that we don't have any room to move, then + // just return. + if (dx == 0 && dy == 0) + return false; + + // the viewingRect moves opposite of the actual pan direction, see above + vr.x += dx; + vr.y += dy; + + // Go through each widget and move it by dx,dy. Frozen widgets + // will be ignored in commitState. + // The widget rects are in real stack space though, so we need to subtract + // our (now negated) dx, dy from their coordinates. + for (let wid in this._widgetState) { + let state = this._widgetState[wid]; + if (!state.ignoreX) + state.rect.x -= dx; + if (!state.ignoreY) + state.rect.y -= dy; + + this._commitState(state); + } + + /* Do not call panhandler during pans within a transaction. + * Those pans always end-up covering up the checkerboard and + * do not require sliding out the location bar + */ + if (!this._skipViewportUpdates && this._panHandler) + this._panHandler.apply(window, [vr.clone(), dx, dy]); + + return true; + }, + + _dragUpdate: function _dragUpdate() { + let dx = this._dragState.outerLastUpdateDX - this._dragState.outerDX; + let dy = this._dragState.outerLastUpdateDY - this._dragState.outerDY; + + this._dragState.outerLastUpdateDX = this._dragState.outerDX; + this._dragState.outerLastUpdateDY = this._dragState.outerDY; + + return this.panBy(dx, dy); + }, + + // + // widget addition/removal + // + _addNewWidget: function (w) { + let wid = w.getAttribute("id"); + if (!wid) { + reportError("WidgetStack: child widget without id!"); + return; + } + + if (w.getAttribute("hidden") == "true") + return; + + let state = { + widget: w, + id: wid, + + viewport: false, + ignoreX: false, + ignoreY: false, + sticky: false, + frozen: false, + vpRelative: false, + + offsetLeft: 0, + offsetTop: 0, + offsetRight: 0, + offsetBottom: 0 + }; + + this._updateWidgetRect(state); + + if (w.hasAttribute("constraint")) { + let cs = w.getAttribute("constraint").split(","); + for (let s of cs) { + if (s == "ignore-x") + state.ignoreX = true; + else if (s == "ignore-y") + state.ignoreY = true; + else if (s == "sticky") + state.sticky = true; + else if (s == "frozen") { + state.frozen = true; + } else if (s == "vp-relative") + state.vpRelative = true; + } + } + + if (w.hasAttribute("viewport")) { + if (this._viewport) + reportError("WidgetStack: more than one viewport canvas in stack!"); + + this._viewport = state; + state.viewport = true; + + if (w.hasAttribute("vptargetx") && w.hasAttribute("vptargety") && + w.hasAttribute("vptargetw") && w.hasAttribute("vptargeth")) + { + let wx = parseInt(w.getAttribute("vptargetx")); + let wy = parseInt(w.getAttribute("vptargety")); + let ww = parseInt(w.getAttribute("vptargetw")); + let wh = parseInt(w.getAttribute("vptargeth")); + + state.offsetLeft = state.rect.left - wx; + state.offsetTop = state.rect.top - wy; + state.offsetRight = state.rect.right - (wx + ww); + state.offsetBottom = state.rect.bottom - (wy + wh); + + state.rect = new wsRect(wx, wy, ww, wh); + } + + // initialize inner bounds to top-left + state.viewportInnerBounds = new wsRect(0, 0, state.rect.width, state.rect.height); + } + + this._widgetState[wid] = state; + + log ("(New widget: " + wid + (state.viewport ? " [viewport]" : "") + " at: " + state.rect + ")"); + }, + + _removeWidget: function (w) { + let wid = w.getAttribute("id"); + delete this._widgetState[wid]; + this._updateWidgets(); + }, + + // updateWidgets: + // Go through all the widgets and figure out their viewport-relative offsets. + // If the widget goes to the left or above the viewport widget, then + // vpOffsetXBefore or vpOffsetYBefore is set. + // See setViewportBounds for use of vpOffset* state variables, and for how + // the actual x and y coords of each widget are calculated based on their offsets + // and the viewport bounds. + _updateWidgets: function () { + let vp = this._viewport; + + let ofRect = this._viewingRect.clone(); + + for (let wid in this._widgetState) { + let state = this._widgetState[wid]; + if (vp && state.vpRelative) { + // compute the vpOffset from 0,0 assuming that the viewport rect is 0,0 + if (state.rect.left >= vp.rect.right) { + state.vpOffsetXBefore = false; + state.vpOffsetX = state.rect.left - vp.rect.width; + } else { + state.vpOffsetXBefore = true; + state.vpOffsetX = state.rect.left - vp.rect.left; + } + + if (state.rect.top >= vp.rect.bottom) { + state.vpOffsetYBefore = false; + state.vpOffsetY = state.rect.top - vp.rect.height; + } else { + state.vpOffsetYBefore = true; + state.vpOffsetY = state.rect.top - vp.rect.top; + } + + log("widget", state.id, "offset", state.vpOffsetX, state.vpOffsetXBefore ? "b" : "a", state.vpOffsetY, state.vpOffsetYBefore ? "b" : "a", "rect", state.rect); + } + } + + this._updateViewportOverflow(); + }, + + // updates the viewportOverflow/pannableBounds + _updateViewportOverflow: function() { + let vp = this._viewport; + if (!vp) + return; + + let ofRect = new wsRect(0, 0, this._viewingRect.width, this._viewingRect.height); + + for (let wid in this._widgetState) { + let state = this._widgetState[wid]; + if (vp && state.vpRelative) { + ofRect.left = Math.min(ofRect.left, state.rect.left); + ofRect.top = Math.min(ofRect.top, state.rect.top); + ofRect.right = Math.max(ofRect.right, state.rect.right); + ofRect.bottom = Math.max(ofRect.bottom, state.rect.bottom); + } + } + + // prevent the viewportOverflow from having positive top/left or negative + // bottom/right values, which would otherwise happen if there aren't widgets + // beyond each of those edges + this._viewportOverflow = new wsBorder( + /* top*/ Math.round(Math.min(ofRect.top, 0)), + /* left*/ Math.round(Math.min(ofRect.left, 0)), + /* bottom*/ Math.round(Math.max(ofRect.bottom - vp.rect.height, 0)), + /* right*/ Math.round(Math.max(ofRect.right - vp.rect.width, 0)) + ); + + // clear the _pannableBounds cache, since it depends on the + // viewportOverflow + this._pannableBounds = null; + }, + + _widgetBounds: function () { + let r = new wsRect(0, 0, 0, 0); + + for (let wid in this._widgetState) { + let state = this._widgetState[wid]; + r = r.union(state.rect); + } + + return r; + }, + + _commitState: function (state) { + // if the widget is frozen, don't actually update its left/top; + // presumably the caller is managing those directly for now. + if (state.frozen) + return; + let w = state.widget; + let l = state.rect.x + state.offsetLeft; + let t = state.rect.y + state.offsetTop; + + // cache left/top to avoid calling setAttribute unnessesarily + if (state._left != l) { + state._left = l; + w.setAttribute("left", l); + } + + if (state._top != t) { + state._top = t; + w.setAttribute("top", t); + } + }, + + // constrain translate of rect by dx dy to bounds; return dx dy that can + // be used to bring rect up to the edge of bounds if we'd go over. + _rectTranslateConstrain: function (dx, dy, rect, bounds) { + let newX, newY; + + // If the rect is larger than the bounds, allow it to increase its overlap + let woverflow = rect.width > bounds.width; + let hoverflow = rect.height > bounds.height; + if (woverflow || hoverflow) { + let intersection = rect.intersect(bounds); + let newIntersection = rect.clone().translate(dx, dy).intersect(bounds); + if (woverflow) + newX = (newIntersection.width > intersection.width) ? rect.x + dx : rect.x; + if (hoverflow) + newY = (newIntersection.height > intersection.height) ? rect.y + dy : rect.y; + } + + // Common case, rect fits within the bounds + // clamp new X to within [bounds.left, bounds.right - rect.width], + // new Y to within [bounds.top, bounds.bottom - rect.height] + if (isNaN(newX)) + newX = Math.min(Math.max(bounds.left, rect.x + dx), bounds.right - rect.width); + if (isNaN(newY)) + newY = Math.min(Math.max(bounds.top, rect.y + dy), bounds.bottom - rect.height); + + return [newX - rect.x, newY - rect.y]; + }, + + // add a new barrier from a <spacer> + _addNewBarrierFromSpacer: function (el) { + let t = el.getAttribute("barriertype"); + + // XXX implement these at some point + // t != "lr" && t != "rl" && + // t != "tb" && t != "bt" && + + if (t != "horizontal" && + t != "vertical") + { + throw "Invalid barrier type: " + t; + } + + let x, y; + + let barrier = {}; + let vp = this._viewport; + + barrier.type = t; + + if (el.getAttribute("left")) + barrier.x = parseInt(el.getAttribute("left")); + else if (el.getAttribute("top")) + barrier.y = parseInt(el.getAttribute("top")); + else + throw "Barrier without top or left attribute"; + + if (el.getAttribute("size")) + barrier.size = parseInt(el.getAttribute("size")); + else + barrier.size = 10; + + if (el.hasAttribute("constraint")) { + let cs = el.getAttribute("constraint").split(","); + for (let s of cs) { + if (s == "ignore-x") + barrier.ignoreX = true; + else if (s == "ignore-y") + barrier.ignoreY = true; + else if (s == "sticky") + barrier.sticky = true; + else if (s == "frozen") { + barrier.frozen = true; + } else if (s == "vp-relative") + barrier.vpRelative = true; + } + } + + if (barrier.vpRelative) { + if (barrier.type == "vertical") { + if (barrier.x >= vp.rect.right) { + barrier.vpOffsetXBefore = false; + barrier.vpOffsetX = barrier.x - vp.rect.right; + } else { + barrier.vpOffsetXBefore = true; + barrier.vpOffsetX = barrier.x - vp.rect.left; + } + } else if (barrier.type == "horizontal") { + if (barrier.y >= vp.rect.bottom) { + barrier.vpOffsetYBefore = false; + barrier.vpOffsetY = barrier.y - vp.rect.bottom; + } else { + barrier.vpOffsetYBefore = true; + barrier.vpOffsetY = barrier.y - vp.rect.top; + } + + // log2("h barrier relative", barrier.vpOffsetYBefore, barrier.vpOffsetY); + } + } + + this._barriers.push(barrier); + } +}; diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/firefoxOverlay.xul b/toolkit/content/tests/fennec-tile-testapp/chrome/content/firefoxOverlay.xul new file mode 100644 index 0000000000..612f8bb9ff --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/firefoxOverlay.xul @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet href="chrome://tile/skin/overlay.css" type="text/css"?> +<!DOCTYPE overlay SYSTEM "chrome://tile/locale/tile.dtd"> +<overlay id="tile-overlay" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="overlay.js"/> + <stringbundleset id="stringbundleset"> + <stringbundle id="tile-strings" src="chrome://tile/locale/tile.properties"/> + </stringbundleset> + + <menupopup id="menu_ToolsPopup"> + <menuitem id="tile-hello" label="&tile.label;" + oncommand="tile.onMenuItemCommand(event);"/> + </menupopup> +</overlay> diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/foo.xul b/toolkit/content/tests/fennec-tile-testapp/chrome/content/foo.xul new file mode 100644 index 0000000000..cdc01658a0 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/foo.xul @@ -0,0 +1,460 @@ +<window
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="onAlmostLoad();"
+ style="background-color:white;"
+ width="800"
+ height="480"
+ onresize="onResize();"
+ onkeypress="onKeyPress(event);">
+
+<script type="application/javascript" src="WidgetStack.js"/>
+<script type="application/javascript" src="TileManager.js"/>
+<script type="application/javascript" src="BrowserView.js"/>
+<script type="application/javascript">
+<![CDATA[
+
+// We do not endorse the use of globals, but this is just a closed lab
+// environment. What could possibly go wrong? ...
+let bv = null;
+let scrollbox = null;
+let leftbar = null;
+let rightbar = null;
+let topbar = null;
+
+function debug() {
+ let w = scrollbox.scrolledWidth;
+ let h = scrollbox.scrolledHeight;
+ let container = document.getElementById("tile_container");
+ let [x, y] = getScrollboxPosition();
+ if (bv) {
+ dump('----------------------DEBUG!-------------------------\n');
+ dump(bv._browserViewportState.toString() + endl);
+
+ dump(endl);
+
+ let cr = bv._tileManager._criticalRect;
+ dump('criticalRect from BV: ' + (cr ? cr.toString() : null) + endl);
+ dump('visibleRect from BV : ' + bv._visibleRect.toString() + endl);
+ dump('visibleRect from foo: ' + scrollboxToViewportRect(getVisibleRect()) + endl);
+
+ dump(endl);
+
+ dump('container width,height from BV: ' + bv._container.style.width + ', '
+ + bv._container.style.height + endl);
+ dump('container width,height via DOM: ' + container.style.width + ', '
+ + container.style.height + endl);
+
+ dump(endl);
+
+ dump('scrollbox position : ' + x + ', ' + y + endl);
+ dump('scrollbox scrolledsize: ' + w + ', ' + h + endl);
+
+ dump(endl);
+
+ dump('tilecache capacity: ' + bv._tileManager._tileCache.getCapacity() + endl);
+ dump('tilecache size : ' + bv._tileManager._tileCache.size + endl);
+ dump('tilecache numFree : ' + bv._tileManager._tileCache.numFree + endl);
+ dump('tilecache iBound : ' + bv._tileManager._tileCache.iBound + endl);
+ dump('tilecache jBound : ' + bv._tileManager._tileCache.jBound + endl);
+ dump('tilecache _lru : ' + bv._tileManager._tileCache._lru + endl);
+
+ dump('-----------------------------------------------------\n');
+ }
+}
+
+function debugTile(i, j) {
+ let tc = bv._tileManager._tileCache;
+ let t = tc.getTile(i, j);
+
+ dump('------ DEBUGGING TILE (' + i + ',' + j + ') --------\n');
+
+ dump('in bounds: ' + tc.inBounds(i, j) + endl);
+ dump('occupied : ' + tc._isOccupied(i, j) + endl);
+ if (t)
+ {
+ dump('toString : ' + t.toString(true) + endl);
+ dump('free : ' + t.free + endl);
+ dump('dirtyRect: ' + t._dirtyTileCanvasRect + endl);
+
+ let len = tc._tilePool.length;
+ for (let k = 0; k < len; ++k)
+ if (tc._tilePool[k] === t)
+ dump('found in tilePool at index ' + k + endl);
+ }
+
+ dump('------------------------------------\n');
+}
+
+function onKeyPress(e) {
+ const a = 97; // debug all critical tiles
+ const c = 99; // set tilecache capacity
+ const d = 100; // debug dump
+ const f = 102; // run noop() through forEachIntersectingRect (for timing)
+ const i = 105; // toggle info click mode
+ const l = 108; // restart lazy crawl
+ const m = 109; // fix mouseout
+ const t = 116; // debug given list of tiles separated by space
+
+ switch (e.charCode) {
+ case d:
+ debug();
+
+ break;
+ case l:
+ bv._tileManager.restartLazyCrawl(bv._tileManager._criticalRect);
+
+ break;
+ case c:
+ let cap = parseInt(window.prompt('new capacity'));
+ bv._tileManager._tileCache.setCapacity(cap);
+
+ break;
+ case f:
+ let noop = function noop() { for (let i = 0; i < 10; ++i); };
+ bv._tileManager._tileCache.forEachIntersectingRect(bv._tileManager._criticalRect,
+ false, noop, window);
+
+ break;
+ case t:
+ let ijstrs = window.prompt('row,col plz').split(' ');
+ for (let ijstr of ijstrs) {
+ let [i, j] = ijstr.split(',').map(x => parseInt(x));
+ debugTile(i, j);
+ }
+
+ break;
+ case a:
+ let cr = bv._tileManager._criticalRect;
+ dump('>>>>>> critical rect is ' + (cr ? cr.toString() : cr) + endl);
+ if (cr) {
+ let starti = cr.left >> kTileExponentWidth;
+ let endi = cr.right >> kTileExponentWidth;
+
+ let startj = cr.top >> kTileExponentHeight;
+ let endj = cr.bottom >> kTileExponentHeight;
+
+ for (var jj = startj; jj <= endj; ++jj)
+ for (var ii = starti; ii <= endi; ++ii)
+ debugTile(ii, jj);
+ }
+
+ break;
+ case i:
+ window.infoMode = !window.infoMode;
+ break;
+ case m:
+ onMouseUp();
+ break;
+ default:
+ break;
+ }
+}
+
+function onResize(e) {
+ if (bv) {
+ bv.beginBatchOperation();
+ bv.setVisibleRect(scrollboxToViewportRect(getVisibleRect()));
+ bv.zoomToPage();
+ bv.commitBatchOperation();
+ }
+}
+
+function onMouseDown(e) {
+ if (window.infoMode) {
+ let [basex, basey] = getScrollboxPosition();
+ let [x, y] = scrollboxToViewportXY(basex + e.clientX, basey + e.clientY);
+ let i = x >> kTileExponentWidth;
+ let j = y >> kTileExponentHeight;
+
+ debugTile(i, j);
+ }
+
+ window._isDragging = true;
+ window._dragStart = {x: e.clientX, y: e.clientY};
+
+ bv.pauseRendering();
+}
+
+function onMouseUp() {
+ window._isDragging = false;
+ bv.resumeRendering();
+}
+
+function onMouseMove(e) {
+ if (window._isDragging) {
+ let x = scrollbox.positionX;
+ let y = scrollbox.positionY;
+ let w = scrollbox.scrolledWidth;
+ let h = scrollbox.scrolledHeight;
+
+ let dx = window._dragStart.x - e.clientX;
+ let dy = window._dragStart.y - e.clientY;
+
+ // XXX if max(x, 0) > scrollwidth we shouldn't do anything (same for y/height)
+ let newX = Math.max(x + dx, 0);
+ let newY = Math.max(y + dy, 0);
+
+ if (newX < w || newY < h) {
+ // clip dx and dy to prevent us from going below 0
+ dx = Math.max(dx, -x);
+ dy = Math.max(dy, -y);
+
+ let oldx = x;
+ let oldy = y;
+
+ bv.onBeforeVisibleMove(dx, dy);
+
+ updateBars(oldx, oldy, dx, dy);
+ scrollbox.scrollBy(dx, dy);
+
+ let [newx, newy] = getScrollboxPosition();
+ let realdx = newx - oldx;
+ let realdy = newy - oldy;
+
+ updateBars(oldx, oldy, realdx, realdy);
+ bv.onAfterVisibleMove(realdx, realdy);
+ }
+ window._dragStart = {x: e.clientX, y: e.clientY};
+ }
+}
+
+function onAlmostLoad() {
+ window._isDragging = false;
+ window.infoMode = false;
+ window.setTimeout(onLoad, 1500);
+}
+
+function onLoad() {
+ // ----------------------------------------------------
+ scrollbox = document.getElementById("scrollbox").boxObject;
+ leftbar = document.getElementById("left_sidebar");
+ rightbar = document.getElementById("right_sidebar");
+ topbar = document.getElementById("top_urlbar");
+ // ----------------------------------------------------
+
+ let initX = Math.round(leftbar.getBoundingClientRect().right);
+ dump('scrolling to ' + initX + endl);
+ scrollbox.scrollTo(initX, 0);
+ let [x, y] = getScrollboxPosition();
+ dump(' scrolled to ' + x + ',' + y + endl);
+
+ let container = document.getElementById("tile_container");
+ container.addEventListener("mousedown", onMouseDown, true);
+ container.addEventListener("mouseup", onMouseUp, true);
+ container.addEventListener("mousemove", onMouseMove, true);
+
+ bv = new BrowserView(container, scrollboxToViewportRect(getVisibleRect()));
+
+ let browser = document.getElementById("googlenews");
+ bv.setBrowser(browser, false);
+}
+
+function updateBars(x, y, dx, dy) {
+return;
+ // shouldn't update margin if it doesn't need to be changed
+ let sidebars = document.getElementsByClassName("sidebar");
+ for (let i = 0; i < sidebars.length; i++) {
+ let sidebar = sidebars[i];
+ sidebar.style.margin = (y + dy) + "px 0px 0px 0px";
+ }
+
+ let urlbar = document.getElementById("top_urlbar");
+ urlbar.style.margin = "0px 0px 0px " + (x + dx) + "px";
+}
+
+function viewportToScrollboxXY(x, y) {
+ return scrollboxToViewportXY(x, y, -1);
+}
+
+function scrollboxToViewportXY(x, y) {
+ if (!x) x = 0;
+ if (!y) y = 0;
+
+ // shield your eyes!
+ let direction = (arguments.length >= 3) ? arguments[2] : 1;
+
+ let leftbarcr = leftbar.getBoundingClientRect();
+ let rightbarcr = rightbar.getBoundingClientRect();
+ let topbarcr = topbar.getBoundingClientRect();
+
+ let xtrans = direction * (-leftbarcr.width);
+ let ytrans = direction * (-topbarcr.height);
+ x += xtrans;
+ y += ytrans;
+
+ return [x, y];
+}
+
+function scrollboxToBrowserXY(browserView, x, y) {
+ [x, y] = scrollboxToViewportXY(x, y);
+ return [browserView.viewportToBrowser(x),
+ browserView.viewportToBrowser(y)];
+}
+
+function scrollboxToViewportRect(rect) {
+ let leftbarcr = leftbar.getBoundingClientRect();
+ let topbarcr = topbar.getBoundingClientRect();
+
+ let xtrans = -leftbarcr.width;
+ let ytrans = -topbarcr.height;
+
+ rect.translate(xtrans, ytrans);
+
+ return rect;
+}
+
+function getScrollboxPosition() {
+ return [scrollbox.positionX, scrollbox.positionY];
+}
+
+function getContentScrollValues(browser) {
+ let cwu = getBrowserDOMWindowUtils(browser);
+ let scrollX = {};
+ let scrollY = {};
+ cwu.getScrollXY(false, scrollX, scrollY);
+
+ return [scrollX.value, scrollY.value];
+}
+
+function getBrowserDOMWindowUtils(browser) {
+ return browser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+}
+
+function getBrowserClientRect(browser, el) {
+ let [scrollX, scrollY] = getContentScrollValues(browser);
+ let r = el.getBoundingClientRect();
+
+ return new wsRect(r.left + scrollX,
+ r.top + scrollY,
+ r.width, r.height);
+}
+
+function scrollToElement(browser, el) {
+ var elRect = getPagePosition(browser, el);
+ bv.browserToViewportRect(elRect);
+ elRect.round();
+ this.scrollTo(elRect.x, elRect.y);
+}
+
+function zoomToElement(aElement) {
+ const margin = 15;
+
+ let elRect = getBrowserClientRect(browser, aElement);
+ let elWidth = elRect.width;
+ let vrWidth = bv.visibleRect.width;
+ /* Try to set zoom-level such that once zoomed element is as wide
+ * as the visible rect */
+ let zoomLevel = vrtWidth / (elWidth + (2 * margin));
+
+ bv.beginBatchOperation();
+
+ bv.setZoomLevel(zoomLevel);
+
+ /* If zoomLevel ends up clamped to less than asked for, calculate
+ * how many more screen pixels will fit horizontally in addition to
+ * element's width. This ensures that more of the webpage is
+ * showing instead of the navbar. Bug 480595. */
+ let xpadding = Math.max(margin, vrWidth - bv.browserToViewport(elWidth));
+
+ // XXX TODO these arguments are wrong, we still have to transform the coordinates
+ // from viewport to scrollbox before sending them to scrollTo
+ this.scrollTo(Math.floor(Math.max(bv.browserToViewport(elRect.x) - xpadding, 0)),
+ Math.floor(Math.max(bv.browserToViewport(elRect.y) - margin, 0)));
+
+ bv.commitBatchOperation();
+}
+
+function zoomFromElement(browser, aElement) {
+ let elRect = getBrowserClientRect(browser, aElement);
+
+ bv.beginBatchOperation();
+
+ // pan to the element
+ // don't bother with x since we're zooming all the way out
+ bv.zoomToPage();
+
+ // XXX have this center the element on the page
+ // XXX TODO these arguments are wrong, we still have to transform the coordinates
+ // from viewport to scrollbox before sending them to scrollTo
+ this.scrollTo(0, Math.floor(Math.max(0, bv.browserToViewport(elRect.y))));
+
+ bv.commitBatchOperation();
+}
+
+/**
+ * Retrieve the content element for a given point in client coordinates
+ * (relative to the top left corner of the chrome window).
+ */
+function elementFromPoint(browser, browserView, x, y) {
+ [x, y] = scrollboxToBrowserXY(browserView, x, y);
+ let cwu = getBrowserDOMWindowUtils(browser);
+ return cwu.elementFromPoint(x, y,
+ true, /* ignore root scroll frame*/
+ false); /* don't flush layout */
+}
+
+/* ensures that a given content element is visible */
+function ensureElementIsVisible(browser, aElement) {
+ let elRect = getBrowserClientRect(browser, aElement);
+
+ bv.browserToViewportRect(elRect);
+
+ let curRect = bv.visibleRect;
+ let newx = curRect.x;
+ let newy = curRect.y;
+
+ if (elRect.x < curRect.x || elRect.width > curRect.width) {
+ newx = elRect.x;
+ } else if (elRect.x + elRect.width > curRect.x + curRect.width) {
+ newx = elRect.x - curRect.width + elRect.width;
+ }
+
+ if (elRect.y < curRect.y || elRect.height > curRect.height) {
+ newy = elRect.y;
+ } else if (elRect.y + elRect.height > curRect.y + curRect.height) {
+ newy = elRect.y - curRect.height + elRect.height;
+ }
+
+ // XXX TODO these arguments are wrong, we still have to transform the coordinates
+ // from viewport to scrollbox before sending them to scrollTo
+ this.scrollTo(newx, newy);
+}
+
+// this is a mehful way of getting the visible rect in scrollbox coordinates
+// that we use in this here lab environment and hopefully nowhere in real fennec
+function getVisibleRect() {
+ let w = window.innerWidth;
+ let h = window.innerHeight;
+
+ let [x, y] = getScrollboxPosition();
+
+ return new wsRect(x, y, w, h);
+}
+
+]]>
+</script>
+
+<scrollbox id="scrollbox" style="-moz-box-orient: vertical; overflow: scroll;" flex="1">
+ <hbox id="top_urlbar" style="background-color: pink"><textbox flex="1"/></hbox>
+ <hbox style="position: relative">
+ <vbox id="left_sidebar" class="sidebar" style="background-color: red"><button label="left sidebar"/></vbox>
+ <box>
+ <html:div id="tile_container" style="position: relative; width: 800px; height: 480px; overflow: -moz-hidden-unscrollable;"/>
+ </box>
+ <vbox id="right_sidebar" class="sidebar" style="background-color: blue"><button label="right sidebar"/></vbox>
+ </hbox>
+</scrollbox>
+
+ <box>
+ <html:div style="position: relative; overflow: hidden; max-width: 0px; max-height: 0px; visibility: hidden;">
+ <html:div id="browsers" style="position: absolute;">
+ <!-- <browser id="googlenews" src="http://www.webhamster.com/" type="content" remote="true" style="width: 1024px; height: 614px"/> -->
+ <iframe id="googlenews" src="http://news.google.com/" type="content" remote="true" style="width: 1024px; height: 614px"/>
+ </html:div>
+ </html:div>
+ </box>
+
+</window>
diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/main.xul b/toolkit/content/tests/fennec-tile-testapp/chrome/content/main.xul new file mode 100644 index 0000000000..f829b3f4a1 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/main.xul @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window id="main" title="My App" width="300" height="300" +xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <caption label="Hello World"/> +</window> diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/content/overlay.js b/toolkit/content/tests/fennec-tile-testapp/chrome/content/overlay.js new file mode 100644 index 0000000000..8dd09af00d --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/content/overlay.js @@ -0,0 +1,15 @@ +var tile = { + onLoad: function() { + // initialization code + this.initialized = true; + this.strings = document.getElementById("tile-strings"); + }, + onMenuItemCommand: function(e) { + var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Components.interfaces.nsIPromptService); + promptService.alert(window, this.strings.getString("helloMessageTitle"), + this.strings.getString("helloMessage")); + }, + +}; +window.addEventListener("load", function(e) { tile.onLoad(e); }, false); diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.dtd b/toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.dtd new file mode 100644 index 0000000000..8cffbce359 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.dtd @@ -0,0 +1 @@ +<!ENTITY tile.label "Your localized menuitem"> diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.properties b/toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.properties new file mode 100644 index 0000000000..72062a4f0b --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/locale/en-US/tile.properties @@ -0,0 +1,3 @@ +helloMessage=Hello World! +helloMessageTitle=Hello +prefMessage=Int Pref Value: %d diff --git a/toolkit/content/tests/fennec-tile-testapp/chrome/skin/overlay.css b/toolkit/content/tests/fennec-tile-testapp/chrome/skin/overlay.css new file mode 100644 index 0000000000..98718057f4 --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/chrome/skin/overlay.css @@ -0,0 +1,5 @@ +/* This is just an example. You shouldn't do this. */ +#tile-hello +{ + color: red ! important; +} diff --git a/toolkit/content/tests/fennec-tile-testapp/defaults/preferences/prefs.js b/toolkit/content/tests/fennec-tile-testapp/defaults/preferences/prefs.js new file mode 100644 index 0000000000..823196f8bf --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/defaults/preferences/prefs.js @@ -0,0 +1,2 @@ +pref("toolkit.defaultChromeURI", "chrome://tile/content/foo.xul"); +pref("browser.dom.window.dump.enabled", true); diff --git a/toolkit/content/tests/fennec-tile-testapp/install.rdf b/toolkit/content/tests/fennec-tile-testapp/install.rdf new file mode 100644 index 0000000000..e80fb845cc --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/install.rdf @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>tile@roy</em:id> + <em:name>tile</em:name> + <em:version>1.0</em:version> + <em:creator>Roy</em:creator> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!-- firefox --> + <em:minVersion>1.5</em:minVersion> + <em:maxVersion>3.5.*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF> diff --git a/toolkit/content/tests/fennec-tile-testapp/logread.py b/toolkit/content/tests/fennec-tile-testapp/logread.py new file mode 100644 index 0000000000..afa1fa524b --- /dev/null +++ b/toolkit/content/tests/fennec-tile-testapp/logread.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +import re, sys + +interesting_re = re.compile("(js_Execute|CallHook) ([^ ]+) ([^ ]+ )?([^ ]+ms)") +class Entry: + def __init__(self, kind, depth, file, linenum, func, timetaken): + self.kind = kind + self.depth = depth + self.file = file + self.linenum = linenum + self.func = func + self.timetaken = timetaken + self.calls = 0 + self.duration = 0 + + def __str__(self): + return " ".join(map(str,[self.kind, self.depth, self.file, self.linenum, self.func, self.timetaken])) + + def id(self): + if self.kind == "js_Execute": + return self.file + else: + if self.file and self.linenum: + strout = "%s:%d" % (self.file, self.linenum) + if self.func: + strout = "%s %s" % (self.func, strout) + return strout + elif self.func: + return self.func + else: + print("No clue what my id is:"+self) + + def call(self, timetaken): + self.calls += 1 + self.duration += timetaken + +def parse_line(line): + m = interesting_re.search(line) + if not m: + return None + + ms_index = line.find("ms") + depth = m.start() - ms_index - 3 + kind = m.group(1) + func = None + file = None + linenum = None + if kind == "CallHook": + func = m.group(2) + file = m.group(3) + colpos = file.rfind(":") + (file,linenum) = file[:colpos], file[colpos+1:-1] + if linenum == "0": + linenum = None + else: + linenum = int(linenum) + offset = 1 + else: + file = m.group(3) + + timetaken = None + try: + timetaken = float(m.group(4)[:-2]) + except: + return None + return Entry(kind, depth, file, linenum, func, timetaken) + +def compare(x,y): + diff = x[1].calls - y[1].calls + if diff == 0: + return int(x[1].duration - y[1].duration) + elif diff > 0: + return 1 + elif diff < 0: + return -1 + +def frequency(ls): + dict = {} + for item in ls: + id = item.id() + stat = None + if not id in dict: + stat = dict[id] = item + else: + stat = dict[id] + stat.call(item.timetaken) + + ls = dict.items() + ls.sort(compare) + ls = filter(lambda (_,item): item.duration > 20, ls) +# ls = filter(lambda (_,item): item.file and item.file.find("browser.js") != -1 and item.linenum <= 1223 and item.linenum >1067, ls) + for key, item in ls: + print(item.calls,key, str(item.duration)+"ms") + +def go(): + file = sys.argv[1] + + ls = filter(lambda x: x != None, map(parse_line, open(file).readlines())) + + frequency(ls) + print ls[0] + +go() + diff --git a/toolkit/content/tests/mochitest/mochitest.ini b/toolkit/content/tests/mochitest/mochitest.ini new file mode 100644 index 0000000000..1ef68c022d --- /dev/null +++ b/toolkit/content/tests/mochitest/mochitest.ini @@ -0,0 +1,5 @@ +[test_autocomplete_change_after_focus.html] +skip-if = toolkit == "android" +[test_mousecapture.xhtml] +skip-if = toolkit == "android" + diff --git a/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html b/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html new file mode 100644 index 0000000000..540eeacf4b --- /dev/null +++ b/toolkit/content/tests/mochitest/test_autocomplete_change_after_focus.html @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=998893 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 998893 - Ensure that input.value changes affect autocomplete</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript"> + /** Test for Bug 998893 **/ + add_task(function* waitForFocus() { + yield new Promise(resolve => SimpleTest.waitForFocus(resolve)); + }); + + add_task(function* setup() { + yield new Promise(resolve => { + let chromeScript = SpecialPowers.loadChromeScript(function() { + const {FormHistory} = Components.utils.import("resource://gre/modules/FormHistory.jsm", null); + FormHistory.update([ + { op : "bump", fieldname: "field1", value: "Default text option" }, + { op : "bump", fieldname: "field1", value: "New value option" }, + ], { + handleCompletion: function() { + sendAsyncMessage("Test:Resume"); + }, + }); + }); + + chromeScript.addMessageListener("Test:Resume", function resumeListener() { + chromeScript.removeMessageListener("Test:Resume", resumeListener); + chromeScript.destroy(); + resolve(); + }); + }); + }); + + add_task(function* runTest() { + let promisePopupShown = new Promise(resolve => { + let chromeScript = SpecialPowers.loadChromeScript(function() { + Components.utils.import("resource://gre/modules/Services.jsm"); + let window = Services.wm.getMostRecentWindow("navigator:browser"); + let popup = window.document.getElementById("PopupAutoComplete"); + popup.addEventListener("popupshown", function popupShown() { + popup.removeEventListener("popupshown", popupShown); + sendAsyncMessage("Test:Resume"); + }); + }); + + chromeScript.addMessageListener("Test:Resume", function resumeListener() { + chromeScript.removeMessageListener("Test:Resume", resumeListener); + chromeScript.destroy(); + resolve(); + }); + }); + + let field = document.getElementById("field1"); + + let promiseFieldFocus = new Promise(resolve => { + field.addEventListener("focus", function onFocus() { + info("field focused"); + field.value = "New value"; + sendKey("DOWN"); + resolve(); + }); + }); + + let handleEnterPromise = new Promise(resolve => { + function handleEnter(evt) { + if (evt.keyCode != KeyEvent.DOM_VK_RETURN) { + return; + } + info("RETURN received for phase: " + evt.eventPhase); + is(evt.target.value, "New value option", "Check that the correct autocomplete entry was used"); + resolve(); + } + + field.addEventListener("keypress", handleEnter, true); + }); + + field.focus(); + + yield promiseFieldFocus; + + yield promisePopupShown; + + sendKey("DOWN"); + sendKey("RETURN"); + sendKey("RETURN"); + + yield handleEnterPromise; + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=998893">Mozilla Bug 998893</a> +<p id="display"><input id="field1" value="Default text"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/mochitest/test_mousecapture.xhtml b/toolkit/content/tests/mochitest/test_mousecapture.xhtml new file mode 100644 index 0000000000..d4ae945bb3 --- /dev/null +++ b/toolkit/content/tests/mochitest/test_mousecapture.xhtml @@ -0,0 +1,340 @@ +<?xml version="1.0"?> +<!DOCTYPE HTML> +<html xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Mouse Capture Tests</title> + <link rel="stylesheet" href="chrome://global/skin/" type="text/css"/> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body id="body" xmlns="http://www.w3.org/1999/xhtml"> + <p id="display"/><div id="content" style="display: none"/><pre id="test"/> + +<script><![CDATA[ + +SimpleTest.expectAssertions(6, 12); + +SimpleTest.waitForExplicitFinish(); + +var captureRetargetMode = false; +var cachedMouseDown = null; +var previousWidth = 0, originalWidth = 0; +var loadInWindow = false; + +function splitterCallback(adjustment) +{ + var newWidth = Number($("leftbox").width); // getBoundingClientRect().width; + var expectedWidth = previousWidth + adjustment; + if (expectedWidth > $("splitterbox").getBoundingClientRect().width) + expectedWidth = $("splitterbox").getBoundingClientRect().width - $("splitter").getBoundingClientRect().width; + is(newWidth, expectedWidth, "splitter left box size (" + adjustment + ")"); + previousWidth = newWidth; +} + +function selectionCallback(adjustment) +{ + if (adjustment == 4000) { + is(frames[0].getSelection().toString(), "This is some text", "selection after drag (" + adjustment + ")"); + ok(frames[0].scrollY > 40, "selection caused scroll down (" + adjustment + ")"); + } + else { + if (adjustment == 0) { + is(frames[0].getSelection().toString(), ".", "selection after drag (" + adjustment + ")"); + } + is(frames[0].scrollY, 0, "selection scrollY (" + adjustment + ")"); + } +} + +function framesetCallback(adjustment) +{ + var newWidth = frames[1].frames[0].document.documentElement.clientWidth; + var expectedWidth = originalWidth + adjustment; + if (adjustment == 0) + expectedWidth = originalWidth - 12; + else if (expectedWidth >= 4000) + expectedWidth = originalWidth * 2 - 2; + + ok(Math.abs(newWidth - expectedWidth) <= 1, "frameset after drag (" + adjustment + "), new width " + newWidth + ", expected " + expectedWidth); +} + +var otherWindow = null; + +function selectionScrollCheck() +{ + var element = otherWindow.document.documentElement; + + var count = 0; + function selectionScrollDone() { + // wait for 6 scroll events to occur + if (count++ < 6) + return; + + otherWindow.removeEventListener("scroll", selectionScrollDone, false); + + var selectedText = otherWindow.getSelection().toString().replace(/\r/g, ""); + is(selectedText, "One\n\nTwo", "text is selected"); + + // should have scrolled 20 pixels from the mousemove above and at least 6 + // extra 20-pixel increments from the selection scroll timer. "At least 6" + // because we waited for 6 scroll events but multiple scrolls could get + // coalesced into a single scroll event, and paints could be delayed when + // the window loads when the compositor is busy. As a result, we have no + // real guarantees about the upper bound here, and as the upper bound is + // not important for what we're testing here, we don't check it. + var scrollY = otherWindow.scrollY; + info(`Scrolled ${scrollY} pixels`); + ok(scrollY >= 140, "selection scroll position after timer is at least 140"); + ok((scrollY % 20) == 0, "selection scroll position after timer is multiple of 20"); + + synthesizeMouse(element, 4, otherWindow.innerHeight + 25, { type: "mouseup" }, otherWindow); + disableNonTestMouseEvents(false); + otherWindow.close(); + + if (loadInWindow) { + SimpleTest.finish(); + } + else { + // now try again, but open the page in a new window + loadInWindow = true; + synthesizeMouse(document.getElementById("custom"), 2, 2, { type: "mousedown" }); + + // check to ensure that selection dragging scrolls the right scrollable area + otherWindow = window.open("data:text/html,<html><p>One</p><p style='margin-top: 200px;'>Two</p><p style='margin-top: 4000px'>This is some text</p></html>", "_blank", "width=200,height=200,scrollbars=yes"); + SimpleTest.waitForFocus(selectionScrollCheck, otherWindow); + } + } + + SimpleTest.executeSoon(function () { + disableNonTestMouseEvents(true); + synthesizeMouse(element, 2, 2, { type: "mousedown" }, otherWindow); + synthesizeMouse(element, 100, otherWindow.innerHeight + 20, { type: "mousemove" }, otherWindow); + otherWindow.addEventListener("scroll", selectionScrollDone, false); + }); +} + +function runTests() +{ + previousWidth = $("leftbox").getBoundingClientRect().width; + runCaptureTest($("splitter"), splitterCallback); + + var custom = document.getElementById("custom"); + runCaptureTest(custom); + + synthesizeMouseExpectEvent($("rightbox"), 2, 2, { type: "mousemove" }, + $("rightbox"), "mousemove", "setCapture and releaseCapture"); + + custom.setCapture(); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + $("leftbox"), "mousemove", "setCapture fails on non mousedown"); + + var custom2 = document.getElementById("custom2"); + synthesizeMouse(custom2, 2, 2, { type: "mousedown" }); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + $("leftbox"), "mousemove", "document.releaseCapture releases capture"); + + var custom3 = document.getElementById("custom3"); + synthesizeMouse(custom3, 2, 2, { type: "mousedown" }); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + $("leftbox"), "mousemove", "element.releaseCapture releases capture"); + + var custom4 = document.getElementById("custom4"); + synthesizeMouse(custom4, 2, 2, { type: "mousedown" }); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + custom4, "mousemove", "element.releaseCapture during mousemove before releaseCapture"); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + $("leftbox"), "mousemove", "element.releaseCapture during mousemove after releaseCapture"); + + var custom5 = document.getElementById("custom5"); + runCaptureTest(custom5); + captureRetargetMode = true; + runCaptureTest(custom5); + captureRetargetMode = false; + + var custom6 = document.getElementById("custom6"); + synthesizeMouse(custom6, 2, 2, { type: "mousedown" }); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + $("leftbox"), "mousemove", "setCapture only works on elements in documents"); + synthesizeMouse(custom6, 2, 2, { type: "mouseup" }); + + // test that mousedown on an image with setCapture followed by a big enough + // mouse move does not start a drag (bug 517737) + var image = document.getElementById("image"); + image.scrollIntoView(); + synthesizeMouse(image, 2, 2, { type: "mousedown" }); + synthesizeMouseExpectEvent($("leftbox"), 2, 2, { type: "mousemove" }, + image, "mousemove", "setCapture works on images"); + synthesizeMouse(image, 2, 2, { type: "mouseup" }); + + window.scroll(0, 0); + + // save scroll + var scrollX = parent ? parent.scrollX : 0; + var scrollY = parent ? parent.scrollY : 0; + + var b = frames[0].document.getElementById("b"); +// runCaptureTest(b, selectionCallback); + + // restore scroll + if (parent) parent.scroll(scrollX, scrollY); + +// frames[0].getSelection().collapseToStart(); + + var body = frames[0].document.body; + var fixed = frames[0].document.getElementById("fixed"); + function captureOnBody() { body.setCapture() } + body.addEventListener("mousedown", captureOnBody, true); + synthesizeMouse(body, 8, 8, { type: "mousedown" }, frames[0]); + body.removeEventListener("mousedown", captureOnBody, true); + synthesizeMouseExpectEvent(fixed, 2, 2, { type: "mousemove" }, + fixed, "mousemove", "setCapture on body retargets to root node", frames[0]); + synthesizeMouse(body, 8, 8, { type: "mouseup" }, frames[0]); + + previousWidth = frames[1].frames[0].document.documentElement.clientWidth; + originalWidth = previousWidth; + runCaptureTest(frames[1].document.documentElement.lastChild, framesetCallback); + + // ensure that clicking on an element where the frame disappears doesn't crash + synthesizeMouse(frames[2].document.getElementById("input"), 8, 8, { type: "mousedown" }, frames[2]); + synthesizeMouse(frames[2].document.getElementById("input"), 8, 8, { type: "mouseup" }, frames[2]); + + var select = document.getElementById("select"); + select.scrollIntoView(); + + synthesizeMouse(document.getElementById("option3"), 2, 2, { type: "mousedown" }); + synthesizeMouse(document.getElementById("option3"), 2, 1000, { type: "mousemove" }); + is(select.selectedIndex, 2, "scroll select"); + synthesizeMouse(document.getElementById("select"), 2, 2, { type: "mouseup" }); + window.scroll(0, 0); + + synthesizeMouse(custom, 2, 2, { type: "mousedown" }); + + // check to ensure that selection dragging scrolls the right scrollable area. + // This should open the page in a new tab. + + var topPos = window.innerHeight; + otherWindow = window.open("data:text/html,<html><p>One</p><p style='margin-top: " + topPos + "'>Two</p><p style='margin-top: 4000px'>This is some text</p></html>", "_blank"); + SimpleTest.waitForFocus(selectionScrollCheck, otherWindow); +} + +function runCaptureTest(element, callback) +{ + var expectedTarget = null; + + var win = element.ownerDocument.defaultView; + + function mouseMoved(event) { + is(event.originalTarget, expectedTarget, + expectedTarget.id + " target for point " + event.clientX + "," + event.clientY); + } + win.addEventListener("mousemove", mouseMoved, false); + + expectedTarget = element; + + var basepoint = element.localName == "frameset" ? 50 : 2; + synthesizeMouse(element, basepoint, basepoint, { type: "mousedown" }, win); + + // in setCapture(true) mode, all events should fire on custom5. In + // setCapture(false) mode, events can fire at a descendant + if (expectedTarget == $("custom5") && !captureRetargetMode) + expectedTarget = $("custom5spacer"); + + // releaseCapture should do nothing for an element which isn't capturing + $("splitterbox").releaseCapture(); + + synthesizeMouse(element, basepoint + 2, basepoint + 2, { type: "mousemove" }, win); + if (callback) + callback(2); + + if (expectedTarget == $("custom5spacer") && !captureRetargetMode) + expectedTarget = $("custom5inner"); + + if (element.id == "b") { + var tooltip = document.getElementById("tooltip"); + tooltip.openPopup(); + tooltip.hidePopup(); + } + + synthesizeMouse(element, basepoint + 25, basepoint + 25, { type: "mousemove" }, win); + if (callback) + callback(25); + + expectedTarget = element.localName == "b" ? win.document.documentElement : element; + synthesizeMouse(element, basepoint + 4000, basepoint + 4000, { type: "mousemove" }, win); + if (callback) + callback(4000); + synthesizeMouse(element, basepoint - 12, basepoint - 12, { type: "mousemove" }, win); + if (callback) + callback(-12); + + expectedTarget = element.localName == "frameset" ? element : win.document.documentElement; + synthesizeMouse(element, basepoint + 30, basepoint + 30, { type: "mouseup" }, win); + synthesizeMouse(win.document.documentElement, 2, 2, { type: "mousemove" }, win); + if (callback) + callback(0); + + win.removeEventListener("mousemove", mouseMoved, false); +} + +SimpleTest.waitForFocus(runTests); + +]]> +</script> + +<xul:vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" align="start"> + <tooltip id="tooltip"> + <label value="Test"/> + </tooltip> + + <hbox id="splitterbox" style="margin-top: 5px;" onmousedown="this.setCapture()"> + <hbox id="leftbox" width="100" flex="1"/> + <splitter id="splitter" height="5"/> + <hbox id="rightbox" width="100" flex="1"/> + </hbox> + + <vbox id="custom" width="10" height="10" onmousedown="this.setCapture(); cachedMouseDown = event;"/> + <vbox id="custom2" width="10" height="10" onmousedown="this.setCapture(); document.releaseCapture();"/> + <vbox id="custom3" width="10" height="10" onmousedown="this.setCapture(); this.releaseCapture();"/> + <vbox id="custom4" width="10" height="10" onmousedown="this.setCapture();" + onmousemove="this.releaseCapture();"/> + <hbox id="custom5" width="40" height="40" + onmousedown="this.setCapture(captureRetargetMode);"> + <spacer id="custom5spacer" width="5"/> + <hbox id="custom5inner" width="35" height="35"/> + </hbox> + <vbox id="custom6" width="10" height="10" + onmousedown="document.createElement('hbox').setCapture();"/> +</xul:vbox> + + <iframe width="100" height="100" + src="data:text/html,%3Cbody style%3D'font-size%3A 40pt%3B'%3E.%3Cb id%3D'b'%3EThis%3C/b%3E is some text%3Cdiv id='fixed' style='position: fixed; left: 55px; top: 5px; width: 10px; height: 10px'%3E.%3C/div%3E%3C/body%3E"/> + + <iframe width="100" height="100" + src="data:text/html,%3Cframeset cols='50%, 50%'%3E%3Cframe src='about:blank'%3E%3Cframe src='about:blank'%3E%3C/frameset%3E"/> + + <iframe width="100" height="100" + src="data:text/html,%3Cinput id='input' onfocus='this.style.display = "none"' style='float: left;'>"/> + + <select id="select" xmlns="http://www.w3.org/1999/xhtml" size="4"> + <option id="option1">One</option> + <option id="option2">Two</option> + <option id="option3">Three</option> + <option id="option4">Four</option> + <option id="option5">Five</option> + <option id="option6">Six</option> + <option id="option7">Seven</option> + <option id="option8">Eight</option> + <option id="option9">Nine</option> + <option id="option10">Ten</option> + </select> + + <img id="image" xmlns="http://www.w3.org/1999/xhtml" + onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" + ondragstart="ok(false, 'should not get a drag when a setCapture is active');" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC"/> + +</body> + +</html> + diff --git a/toolkit/content/tests/moz.build b/toolkit/content/tests/moz.build new file mode 100644 index 0000000000..e540fa11f7 --- /dev/null +++ b/toolkit/content/tests/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini'] + +BROWSER_CHROME_MANIFESTS += ['browser/browser.ini'] + +MOCHITEST_CHROME_MANIFESTS += [ + 'chrome/chrome.ini', + 'widgets/chrome.ini', +] + +MOCHITEST_MANIFESTS += [ + 'mochitest/mochitest.ini', + 'widgets/mochitest.ini', +] diff --git a/toolkit/content/tests/reftests/bug-442419-progressmeter-max-ref.xul b/toolkit/content/tests/reftests/bug-442419-progressmeter-max-ref.xul new file mode 100644 index 0000000000..a034b6e671 --- /dev/null +++ b/toolkit/content/tests/reftests/bug-442419-progressmeter-max-ref.xul @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <progressmeter value="50"/> <!-- default is max = 100 --> +</window> + diff --git a/toolkit/content/tests/reftests/bug-442419-progressmeter-max.xul b/toolkit/content/tests/reftests/bug-442419-progressmeter-max.xul new file mode 100644 index 0000000000..6225964064 --- /dev/null +++ b/toolkit/content/tests/reftests/bug-442419-progressmeter-max.xul @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <progressmeter max="198" value="99"/> <!-- 50% --> +</window> + diff --git a/toolkit/content/tests/reftests/reftest-stylo.list b/toolkit/content/tests/reftests/reftest-stylo.list new file mode 100644 index 0000000000..77caaabfcb --- /dev/null +++ b/toolkit/content/tests/reftests/reftest-stylo.list @@ -0,0 +1,6 @@ +# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing +skip-if(B2G&&browserIsRemote) random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max.xul +# fails most of the time on Mac because progress meter animates +# Bug 974780 +skip-if(B2G&&browserIsRemote) == textbox-multiline-default-value.xul textbox-multiline-default-value.xul +# Bug 974780 diff --git a/toolkit/content/tests/reftests/reftest.list b/toolkit/content/tests/reftests/reftest.list new file mode 100644 index 0000000000..a37a9722a5 --- /dev/null +++ b/toolkit/content/tests/reftests/reftest.list @@ -0,0 +1,2 @@ +random-if(cocoaWidget) == bug-442419-progressmeter-max.xul bug-442419-progressmeter-max-ref.xul # fails most of the time on Mac because progress meter animates +!= textbox-multiline-default-value.xul textbox-multiline-empty.xul diff --git a/toolkit/content/tests/reftests/textbox-multiline-default-value.xul b/toolkit/content/tests/reftests/textbox-multiline-default-value.xul new file mode 100644 index 0000000000..31a5bc556f --- /dev/null +++ b/toolkit/content/tests/reftests/textbox-multiline-default-value.xul @@ -0,0 +1,5 @@ +<?xml version='1.0'?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="test textbox multiline"> + <textbox multiline='true' value='foobar'></textbox> +</window> diff --git a/toolkit/content/tests/reftests/textbox-multiline-empty.xul b/toolkit/content/tests/reftests/textbox-multiline-empty.xul new file mode 100644 index 0000000000..c48f2c988f --- /dev/null +++ b/toolkit/content/tests/reftests/textbox-multiline-empty.xul @@ -0,0 +1,5 @@ +<?xml version='1.0'?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="test textbox multiline"> + <textbox multiline='true'></textbox> +</window> diff --git a/toolkit/content/tests/unit/.eslintrc.js b/toolkit/content/tests/unit/.eslintrc.js new file mode 100644 index 0000000000..fee088c179 --- /dev/null +++ b/toolkit/content/tests/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/toolkit/content/tests/unit/test_contentAreaUtils.js b/toolkit/content/tests/unit/test_contentAreaUtils.js new file mode 100644 index 0000000000..970e779ce8 --- /dev/null +++ b/toolkit/content/tests/unit/test_contentAreaUtils.js @@ -0,0 +1,80 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* 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/. */ + +var Ci = Components.interfaces; +var Cc = Components.classes; +var Cr = Components.results; + +function loadUtilsScript() { + var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + loader.loadSubScript("chrome://global/content/contentAreaUtils.js"); +} + +function test_urlSecurityCheck() { + var nullPrincipal = Cc["@mozilla.org/nullprincipal;1"]. + createInstance(Ci.nsIPrincipal); + + const HTTP_URI = "http://www.mozilla.org/"; + const CHROME_URI = "chrome://browser/content/browser.xul"; + const DISALLOW_INHERIT_PRINCIPAL = + Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL; + + try { + urlSecurityCheck(makeURI(HTTP_URI), nullPrincipal, + DISALLOW_INHERIT_PRINCIPAL); + } + catch (ex) { + do_throw("urlSecurityCheck should not throw when linking to a http uri with a null principal"); + } + + // urlSecurityCheck also supports passing the url as a string + try { + urlSecurityCheck(HTTP_URI, nullPrincipal, + DISALLOW_INHERIT_PRINCIPAL); + } + catch (ex) { + do_throw("urlSecurityCheck failed to handle the http URI as a string (uri spec)"); + } + + let shouldThrow = true; + try { + urlSecurityCheck(CHROME_URI, nullPrincipal, + DISALLOW_INHERIT_PRINCIPAL); + } + catch (ex) { + shouldThrow = false; + } + if (shouldThrow) + do_throw("urlSecurityCheck should throw when linking to a chrome uri with a null principal"); +} + +function test_stringBundle() { + // This test verifies that the elements that can be used as file picker title + // keys in the save* functions are actually present in the string bundle. + // These keys are part of the contentAreaUtils.js public API. + var validFilePickerTitleKeys = [ + "SaveImageTitle", + "SaveVideoTitle", + "SaveAudioTitle", + "SaveLinkTitle", + ]; + + for (let filePickerTitleKey of validFilePickerTitleKeys) { + // Just check that the string exists + try { + ContentAreaUtils.stringBundle.GetStringFromName(filePickerTitleKey); + } catch (e) { + do_throw("Error accessing file picker title key: " + filePickerTitleKey); + } + } +} + +function run_test() +{ + loadUtilsScript(); + test_urlSecurityCheck(); + test_stringBundle(); +} diff --git a/toolkit/content/tests/unit/xpcshell.ini b/toolkit/content/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..33a0383bd4 --- /dev/null +++ b/toolkit/content/tests/unit/xpcshell.ini @@ -0,0 +1,5 @@ +[DEFAULT] +head = +tail = + +[test_contentAreaUtils.js] diff --git a/toolkit/content/tests/widgets/.eslintrc.js b/toolkit/content/tests/widgets/.eslintrc.js new file mode 100644 index 0000000000..e149193751 --- /dev/null +++ b/toolkit/content/tests/widgets/.eslintrc.js @@ -0,0 +1,8 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../testing/mochitest/mochitest.eslintrc.js", + "../../../../testing/mochitest/chrome.eslintrc.js" + ] +}; diff --git a/toolkit/content/tests/widgets/audio.ogg b/toolkit/content/tests/widgets/audio.ogg Binary files differnew file mode 100644 index 0000000000..a553c23e73 --- /dev/null +++ b/toolkit/content/tests/widgets/audio.ogg diff --git a/toolkit/content/tests/widgets/audio.wav b/toolkit/content/tests/widgets/audio.wav Binary files differnew file mode 100644 index 0000000000..c6fd5cb869 --- /dev/null +++ b/toolkit/content/tests/widgets/audio.wav diff --git a/toolkit/content/tests/widgets/chrome.ini b/toolkit/content/tests/widgets/chrome.ini new file mode 100644 index 0000000000..841b86c0fc --- /dev/null +++ b/toolkit/content/tests/widgets/chrome.ini @@ -0,0 +1,20 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + tree_shared.js + popup_shared.js + window_menubar.xul + seek_with_sound.ogg + +[test_contextmenu_nested.xul] +skip-if = os == 'linux' # Bug 1116215 +[test_contextmenu_menugroup.xul] +skip-if = os == 'linux' # Bug 1115088 +[test_editor_currentURI.xul] +[test_menubar.xul] +skip-if = os == 'mac' +[test_popupanchor.xul] +skip-if = os == 'android' +[test_popupreflows.xul] +[test_tree_column_reorder.xul] +[test_videocontrols_onclickplay.html] diff --git a/toolkit/content/tests/widgets/head.js b/toolkit/content/tests/widgets/head.js new file mode 100644 index 0000000000..c2ae0c7ae1 --- /dev/null +++ b/toolkit/content/tests/widgets/head.js @@ -0,0 +1,23 @@ +"use strict"; + +function waitForCondition(condition, nextTest, errorMsg) { + var tries = 0; + var interval = setInterval(function() { + if (tries >= 30) { + ok(false, errorMsg); + moveOn(); + } + var conditionPassed; + try { + conditionPassed = condition(); + } catch (e) { + ok(false, e + "\n" + e.stack); + conditionPassed = false; + } + if (conditionPassed) { + moveOn(); + } + tries++; + }, 100); + var moveOn = function() { clearInterval(interval); nextTest(); }; +} diff --git a/toolkit/content/tests/widgets/mochitest.ini b/toolkit/content/tests/widgets/mochitest.ini new file mode 100644 index 0000000000..abc77c80ba --- /dev/null +++ b/toolkit/content/tests/widgets/mochitest.ini @@ -0,0 +1,40 @@ +[DEFAULT] +support-files = + audio.wav + audio.ogg + seek_with_sound.ogg + head.js + tree_shared.js + videocontrols_direction-1-ref.html + videocontrols_direction-1a.html + videocontrols_direction-1b.html + videocontrols_direction-1c.html + videocontrols_direction-1d.html + videocontrols_direction-1e.html + videocontrols_direction-2-ref.html + videocontrols_direction-2a.html + videocontrols_direction-2b.html + videocontrols_direction-2c.html + videocontrols_direction-2d.html + videocontrols_direction-2e.html + videocontrols_direction_test.js + videomask.css + +[test_audiocontrols_dimensions.html] +skip-if = toolkit == 'android' +[test_mousecapture_area.html] +[test_videocontrols.html] +tags = fullscreen +skip-if = toolkit == 'android' #TIMED_OUT +[test_videocontrols_vtt.html] +skip-if = toolkit == 'android' +[test_videocontrols_iframe_fullscreen.html] +[test_videocontrols_audio.html] +[test_videocontrols_audio_direction.html] +[test_videocontrols_jsdisabled.html] +skip-if = toolkit == 'android' # bug 1272646 +[test_videocontrols_standalone.html] +skip-if = true # bug 1075573, bug 1262130 +[test_videocontrols_video_direction.html] +skip-if = os == 'win' +[test_bug898940.html] diff --git a/toolkit/content/tests/widgets/popup_shared.js b/toolkit/content/tests/widgets/popup_shared.js new file mode 100644 index 0000000000..49735c5ad8 --- /dev/null +++ b/toolkit/content/tests/widgets/popup_shared.js @@ -0,0 +1,424 @@ +/* + * This script is used for menu and popup tests. Call startPopupTests to start + * the tests, passing an array of tests as an argument. Each test is an object + * with the following properties: + * testname - name of the test + * test - function to call to perform the test + * events - a list of events that are expected to be fired in sequence + * as a result of calling the 'test' function. This list should be + * an array of strings of the form "eventtype targetid" where + * 'eventtype' is the event type and 'targetid' is the id of + * target of the event. This function will be passed two + * arguments, the testname and the step argument. + * Alternatively, events may be a function which returns the array + * of events. This can be used when the events vary per platform. + * result - function to call after all the events have fired to check + * for additional results. May be null. This function will be + * passed two arguments, the testname and the step argument. + * steps - optional array of values. The test will be repeated for + * each step, passing each successive value within the array to + * the test and result functions + * autohide - if set, should be set to the id of a popup to hide after + * the test is complete. This is a convenience for some tests. + * condition - an optional function which, if it returns false, causes the + * test to be skipped. + * end - used for debugging. Set to true to stop the tests after running + * this one. + */ + +const menuactiveAttribute = "_moz-menuactive"; + +var gPopupTests = null; +var gTestIndex = -1; +var gTestStepIndex = 0; +var gTestEventIndex = 0; +var gAutoHide = false; +var gExpectedEventDetails = null; +var gExpectedTriggerNode = null; +var gWindowUtils; +var gPopupWidth = -1, gPopupHeight = -1; + +function startPopupTests(tests) +{ + document.addEventListener("popupshowing", eventOccurred, false); + document.addEventListener("popupshown", eventOccurred, false); + document.addEventListener("popuphiding", eventOccurred, false); + document.addEventListener("popuphidden", eventOccurred, false); + document.addEventListener("command", eventOccurred, false); + document.addEventListener("DOMMenuItemActive", eventOccurred, false); + document.addEventListener("DOMMenuItemInactive", eventOccurred, false); + document.addEventListener("DOMMenuInactive", eventOccurred, false); + document.addEventListener("DOMMenuBarActive", eventOccurred, false); + document.addEventListener("DOMMenuBarInactive", eventOccurred, false); + + gPopupTests = tests; + gWindowUtils = SpecialPowers.getDOMWindowUtils(window); + + goNext(); +} + +function finish() +{ + if (window.opener) { + window.close(); + window.opener.SimpleTest.finish(); + return; + } + SimpleTest.finish(); + return; +} + +function ok(condition, message) { + if (window.opener) + window.opener.SimpleTest.ok(condition, message); + else + SimpleTest.ok(condition, message); +} + +function is(left, right, message) { + if (window.opener) + window.opener.SimpleTest.is(left, right, message); + else + SimpleTest.is(left, right, message); +} + +function disableNonTestMouse(aDisable) { + gWindowUtils.disableNonTestMouseEvents(aDisable); +} + +function eventOccurred(event) +{ + if (gPopupTests.length <= gTestIndex) { + ok(false, "Extra " + event.type + " event fired"); + return; + } + + var test = gPopupTests[gTestIndex]; + if ("autohide" in test && gAutoHide) { + if (event.type == "DOMMenuInactive") { + gAutoHide = false; + setTimeout(goNextStep, 0); + } + return; + } + + var events = test.events; + if (typeof events == "function") + events = events(); + if (events) { + if (events.length <= gTestEventIndex) { + ok(false, "Extra " + event.type + " event fired for " + event.target.id + + " " +gPopupTests[gTestIndex].testname); + return; + } + + var eventitem = events[gTestEventIndex].split(" "); + var matches; + if (eventitem[1] == "#tooltip") { + is(event.originalTarget.localName, "tooltip", + test.testname + " event.originalTarget.localName is 'tooltip'"); + is(event.originalTarget.getAttribute("default"), "true", + test.testname + " event.originalTarget default attribute is 'true'"); + matches = event.originalTarget.localName == "tooltip" && + event.originalTarget.getAttribute("default") == "true"; + } else { + is(event.type, eventitem[0], + test.testname + " event type " + event.type + " fired"); + is(event.target.id, eventitem[1], + test.testname + " event target ID " + event.target.id); + matches = eventitem[0] == event.type && eventitem[1] == event.target.id; + } + + var modifiersMask = eventitem[2]; + if (modifiersMask) { + var m = ""; + m += event.altKey ? '1' : '0'; + m += event.ctrlKey ? '1' : '0'; + m += event.shiftKey ? '1' : '0'; + m += event.metaKey ? '1' : '0'; + is(m, modifiersMask, test.testname + " modifiers mask matches"); + } + + var expectedState; + switch (event.type) { + case "popupshowing": expectedState = "showing"; break; + case "popupshown": expectedState = "open"; break; + case "popuphiding": expectedState = "hiding"; break; + case "popuphidden": expectedState = "closed"; break; + } + + if (gExpectedTriggerNode && event.type == "popupshowing") { + if (gExpectedTriggerNode == "notset") // check against null instead + gExpectedTriggerNode = null; + + is(event.originalTarget.triggerNode, gExpectedTriggerNode, test.testname + " popupshowing triggerNode"); + var isTooltip = (event.target.localName == "tooltip"); + is(document.popupNode, isTooltip ? null : gExpectedTriggerNode, + test.testname + " popupshowing document.popupNode"); + is(document.tooltipNode, isTooltip ? gExpectedTriggerNode : null, + test.testname + " popupshowing document.tooltipNode"); + } + + if (expectedState) + is(event.originalTarget.state, expectedState, + test.testname + " " + event.type + " state"); + + if (matches) { + gTestEventIndex++ + if (events.length <= gTestEventIndex) + setTimeout(checkResult, 0); + } + } +} + +function checkResult() +{ + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) + step = test.steps[gTestStepIndex]; + + if ("result" in test) + test.result(test.testname, step); + + if ("autohide" in test) { + gAutoHide = true; + document.getElementById(test.autohide).hidePopup(); + return; + } + + goNextStep(); +} + +function goNextStep() +{ + gTestEventIndex = 0; + + var step = null; + var test = gPopupTests[gTestIndex]; + if ("steps" in test) { + gTestStepIndex++; + step = test.steps[gTestStepIndex]; + if (gTestStepIndex < test.steps.length) { + test.test(test.testname, step); + return; + } + } + + goNext(); +} + +function goNext() +{ + // We want to continue after the next animation frame so that + // we're in a stable state and don't get spurious mouse events at unexpected targets. + window.requestAnimationFrame( + function() { + setTimeout(goNextStepSync, 0); + } + ); +} + +function goNextStepSync() +{ + if (gTestIndex >= 0 && "end" in gPopupTests[gTestIndex] && gPopupTests[gTestIndex].end) { + finish(); + return; + } + + gTestIndex++; + gTestStepIndex = 0; + if (gTestIndex < gPopupTests.length) { + var test = gPopupTests[gTestIndex]; + // Set the location hash so it's easy to see which test is running + document.location.hash = test.testname; + + // skip the test if the condition returns false + if ("condition" in test && !test.condition()) { + goNext(); + return; + } + + // start with the first step if there are any + var step = null; + if ("steps" in test) + step = test.steps[gTestStepIndex]; + + test.test(test.testname, step); + + // no events to check for so just check the result + if (!("events" in test)) + checkResult(); + } + else { + finish(); + } +} + +function openMenu(menu) +{ + if ("open" in menu) { + menu.open = true; + } + else { + var bo = menu.boxObject; + if (bo instanceof MenuBoxObject) + bo.openMenu(true); + else + synthesizeMouse(menu, 4, 4, { }); + } +} + +function closeMenu(menu, popup) +{ + if ("open" in menu) { + menu.open = false; + } + else { + var bo = menu.boxObject; + if (bo instanceof MenuBoxObject) + bo.openMenu(false); + else + popup.hidePopup(); + } +} + +function checkActive(popup, id, testname) +{ + var activeok = true; + var children = popup.childNodes; + for (var c = 0; c < children.length; c++) { + var child = children[c]; + if ((id == child.id && child.getAttribute(menuactiveAttribute) != "true") || + (id != child.id && child.hasAttribute(menuactiveAttribute) != "")) { + activeok = false; + break; + } + } + ok(activeok, testname + " item " + (id ? id : "none") + " active"); +} + +function checkOpen(menuid, testname) +{ + var menu = document.getElementById(menuid); + if ("open" in menu) + ok(menu.open, testname + " " + menuid + " menu is open"); + else if (menu.boxObject instanceof MenuBoxObject) + ok(menu.getAttribute("open") == "true", testname + " " + menuid + " menu is open"); +} + +function checkClosed(menuid, testname) +{ + var menu = document.getElementById(menuid); + if ("open" in menu) + ok(!menu.open, testname + " " + menuid + " menu is open"); + else if (menu.boxObject instanceof MenuBoxObject) + ok(!menu.hasAttribute("open"), testname + " " + menuid + " menu is closed"); +} + +function convertPosition(anchor, align) +{ + if (anchor == "topleft" && align == "topleft") return "overlap"; + if (anchor == "topleft" && align == "topright") return "start_before"; + if (anchor == "topleft" && align == "bottomleft") return "before_start"; + if (anchor == "topright" && align == "topleft") return "end_before"; + if (anchor == "topright" && align == "bottomright") return "before_end"; + if (anchor == "bottomleft" && align == "bottomright") return "start_after"; + if (anchor == "bottomleft" && align == "topleft") return "after_start"; + if (anchor == "bottomright" && align == "bottomleft") return "end_after"; + if (anchor == "bottomright" && align == "topright") return "after_end"; + return ""; +} + +/* + * When checking position of the bottom or right edge of the popup's rect, + * use this instead of strict equality check of rounded values, + * because we snap the top/left edges to pixel boundaries, + * which can shift the bottom/right up to 0.5px from its "ideal" location, + * and could cause it to round differently. (See bug 622507.) + */ +function isWithinHalfPixel(a, b) +{ + return Math.abs(a - b) <= 0.5; +} + +function compareEdge(anchor, popup, edge, offsetX, offsetY, testname) +{ + testname += " " + edge; + + checkOpen(anchor.id, testname); + + var anchorrect = anchor.getBoundingClientRect(); + var popuprect = popup.getBoundingClientRect(); + var check1 = false, check2 = false; + + if (gPopupWidth == -1) { + ok((Math.round(popuprect.right) - Math.round(popuprect.left)) && + (Math.round(popuprect.bottom) - Math.round(popuprect.top)), + testname + " size"); + } + else { + is(Math.round(popuprect.width), gPopupWidth, testname + " width"); + is(Math.round(popuprect.height), gPopupHeight, testname + " height"); + } + + var spaceIdx = edge.indexOf(" "); + if (spaceIdx > 0) { + let cornerX, cornerY; + let [position, align] = edge.split(" "); + switch (position) { + case "topleft": cornerX = anchorrect.left; cornerY = anchorrect.top; break; + case "topcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.top; break; + case "topright": cornerX = anchorrect.right; cornerY = anchorrect.top; break; + case "leftcenter": cornerX = anchorrect.left; cornerY = anchorrect.top + anchorrect.height / 2; break; + case "rightcenter": cornerX = anchorrect.right; cornerY = anchorrect.top + anchorrect.height / 2; break; + case "bottomleft": cornerX = anchorrect.left; cornerY = anchorrect.bottom; break; + case "bottomcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.bottom; break; + case "bottomright": cornerX = anchorrect.right; cornerY = anchorrect.bottom; break; + } + + switch (align) { + case "topleft": cornerX += offsetX; cornerY += offsetY; break; + case "topright": cornerX += -popuprect.width + offsetX; cornerY += offsetY; break; + case "bottomleft": cornerX += offsetX; cornerY += -popuprect.height + offsetY; break; + case "bottomright": cornerX += -popuprect.width + offsetX; cornerY += -popuprect.height + offsetY; break; + } + + is(Math.round(popuprect.left), Math.round(cornerX), testname + " x position"); + is(Math.round(popuprect.top), Math.round(cornerY), testname + " y position"); + return; + } + + if (edge == "after_pointer") { + is(Math.round(popuprect.left), Math.round(anchorrect.left) + offsetX, testname + " x position"); + is(Math.round(popuprect.top), Math.round(anchorrect.top) + offsetY + 21, testname + " y position"); + return; + } + + if (edge == "overlap") { + ok(Math.round(anchorrect.left) + offsetY == Math.round(popuprect.left) && + Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top), + testname + " position"); + return; + } + + if (edge.indexOf("before") == 0) + check1 = isWithinHalfPixel(anchorrect.top + offsetY, popuprect.bottom); + else if (edge.indexOf("after") == 0) + check1 = (Math.round(anchorrect.bottom) + offsetY == Math.round(popuprect.top)); + else if (edge.indexOf("start") == 0) + check1 = isWithinHalfPixel(anchorrect.left + offsetX, popuprect.right); + else if (edge.indexOf("end") == 0) + check1 = (Math.round(anchorrect.right) + offsetX == Math.round(popuprect.left)); + + if (0 < edge.indexOf("before")) + check2 = (Math.round(anchorrect.top) + offsetY == Math.round(popuprect.top)); + else if (0 < edge.indexOf("after")) + check2 = isWithinHalfPixel(anchorrect.bottom + offsetY, popuprect.bottom); + else if (0 < edge.indexOf("start")) + check2 = (Math.round(anchorrect.left) + offsetX == Math.round(popuprect.left)); + else if (0 < edge.indexOf("end")) + check2 = isWithinHalfPixel(anchorrect.right + offsetX, popuprect.right); + + ok(check1 && check2, testname + " position"); +} diff --git a/toolkit/content/tests/widgets/seek_with_sound.ogg b/toolkit/content/tests/widgets/seek_with_sound.ogg Binary files differnew file mode 100644 index 0000000000..c86d9946bd --- /dev/null +++ b/toolkit/content/tests/widgets/seek_with_sound.ogg diff --git a/toolkit/content/tests/widgets/test_audiocontrols_dimensions.html b/toolkit/content/tests/widgets/test_audiocontrols_dimensions.html new file mode 100644 index 0000000000..0f295cce9b --- /dev/null +++ b/toolkit/content/tests/widgets/test_audiocontrols_dimensions.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Audio controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <audio id="audio" controls preload="auto"></audio> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + function loadedmetadata(event) { + is(event.type, "loadedmetadata", "checking event type"); + is(audio.clientHeight, 28, "checking height of audio element"); + + SimpleTest.finish(); + } + + var audio = document.getElementById("audio"); + + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest); + function startTest() { + // Kick off test once audio has loaded. + audio.addEventListener("loadedmetadata", loadedmetadata, false); + audio.src = "audio.wav"; + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_bug898940.html b/toolkit/content/tests/widgets/test_bug898940.html new file mode 100644 index 0000000000..10a6a80d93 --- /dev/null +++ b/toolkit/content/tests/widgets/test_bug898940.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test that an audio element that's already playing when controls are attached displays the controls</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <audio id="audio" controls src="audio.ogg"></audio> +</div> + +<pre id="test"> +<script class="testbody"> + var audio = document.getElementById("audio"); + audio.play(); + audio.ontimeupdate = function doTest() { + ok(audio.getBoundingClientRect().height > 0, + "checking audio element height is greater than zero"); + audio.ontimeupdate = null; + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_contextmenu_menugroup.xul b/toolkit/content/tests/widgets/test_contextmenu_menugroup.xul new file mode 100644 index 0000000000..594c0264d6 --- /dev/null +++ b/toolkit/content/tests/widgets/test_contextmenu_menugroup.xul @@ -0,0 +1,102 @@ +<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?>
+
+<window title="Context menugroup Tests"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <script type="application/javascript" src="popup_shared.js"></script>
+
+<menupopup id="context">
+ <menugroup>
+ <menuitem id="a"/>
+ <menuitem id="b"/>
+ </menugroup>
+ <menuitem id="c" label="c"/>
+ <menugroup/>
+</menupopup>
+
+<button label="Check"/>
+
+<vbox id="popuparea" popup="context" width="20" height="20"/>
+
+<script type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var gMenuPopup = $("context");
+ok(gMenuPopup, "Got the reference to the context menu");
+
+var popupTests = [
+{
+ testname: "one-down-key",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "popupshowing context", "popupshown context", "DOMMenuItemActive a" ],
+ test: function () {
+ synthesizeMouse($("popuparea"), 4, 4, {});
+ synthesizeKey("VK_DOWN", {});
+ },
+ result: function (testname) {
+ checkActive(gMenuPopup, "a", testname);
+ }
+},
+{
+ testname: "two-down-keys",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive a", "DOMMenuItemActive b" ],
+ test: () => synthesizeKey("VK_DOWN", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "b", testname);
+ }
+},
+{
+ testname: "three-down-keys",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive b", "DOMMenuItemActive c" ],
+ test: () => synthesizeKey("VK_DOWN", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "c", testname);
+ }
+},
+{
+ testname: "three-down-keys-one-up-key",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive c", "DOMMenuItemActive b" ],
+ test: () => synthesizeKey("VK_UP", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "b", testname);
+ }
+},
+{
+ testname: "three-down-keys-two-up-keys",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive b", "DOMMenuItemActive a" ],
+ test: () => synthesizeKey("VK_UP", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "a", testname);
+ }
+},
+{
+ testname: "three-down-keys-three-up-key",
+ condition: function() { return (navigator.platform.indexOf("Mac") == -1); },
+ events: [ "DOMMenuItemInactive a", "DOMMenuItemActive c" ],
+ test: () => synthesizeKey("VK_UP", {}),
+ result: function (testname) {
+ checkActive(gMenuPopup, "c", testname);
+ }
+},
+];
+
+SimpleTest.waitForFocus(function runTest() {
+ startPopupTests(popupTests);
+});
+
+]]>
+</script>
+
+<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body>
+
+</window>
diff --git a/toolkit/content/tests/widgets/test_contextmenu_nested.xul b/toolkit/content/tests/widgets/test_contextmenu_nested.xul new file mode 100644 index 0000000000..9eb42a1eda --- /dev/null +++ b/toolkit/content/tests/widgets/test_contextmenu_nested.xul @@ -0,0 +1,138 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Nested Context Menu Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + +<menupopup id="outercontext"> + <menuitem label="Context One"/> + <menu id="outercontextmenu" label="Sub"> + <menupopup id="innercontext"> + <menuitem id="innercontextmenu" label="Sub Context One"/> + </menupopup> + </menu> +</menupopup> + +<menupopup id="outermain"> + <menuitem label="One"/> + <menu id="outermenu" label="Sub"> + <menupopup id="innermain"> + <menuitem id="innermenu" label="Sub One" context="outercontext"/> + </menupopup> + </menu> +</menupopup> + +<button label="Check"/> + +<vbox id="popuparea" popup="outermain" width="20" height="20"/> + +<script type="application/javascript"> +<![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +var popupTests = [ +{ + testname: "open outer popup", + events: [ "popupshowing outermain", "popupshown outermain" ], + test: () => synthesizeMouse($("popuparea"), 4, 4, {}), + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname); + is(document.popupNode, $("popuparea"), testname + " document.popupNode"); + } +}, +{ + testname: "open inner popup", + events: [ "DOMMenuItemActive outermenu", "popupshowing innermain", "popupshown innermain" ], + test: function () { + synthesizeMouse($("outermenu"), 4, 4, { type: "mousemove" }); + synthesizeMouse($("outermenu"), 2, 2, { type: "mousemove" }); + }, + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, null, testname + " outer context"); + is(document.popupNode, $("popuparea"), testname + " document.popupNode"); + } +}, +{ + testname: "open outer context", + condition: function() { return (navigator.platform.indexOf("Mac") == -1); }, + events: [ "popupshowing outercontext", "popupshown outercontext" ], + test: () => synthesizeMouse($("innermenu"), 4, 4, { type: "contextmenu", button: 2 }), + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, $("innermenu"), testname + " outer context"); + is(document.popupNode, $("innermenu"), testname + " document.popupNode"); + } +}, +{ + testname: "open inner context", + condition: function() { return (navigator.platform.indexOf("Mac") == -1); }, + events: [ "DOMMenuItemActive outercontextmenu", "popupshowing innercontext", "popupshown innercontext" ], + test: function () { + synthesizeMouse($("outercontextmenu"), 4, 4, { type: "mousemove" }); + setTimeout(function() { + synthesizeMouse($("outercontextmenu"), 2, 2, { type: "mousemove" }); + }, 1000); + }, + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, $("innermenu"), testname + " outer context"); + is($("innercontext").triggerNode, $("innermenu"), testname + " inner context"); + is(document.popupNode, $("innermenu"), testname + " document.popupNode"); + } +}, +{ + testname: "close context", + condition: function() { return (navigator.platform.indexOf("Mac") == -1); }, + events: [ "popuphiding innercontext", "popuphidden innercontext", + "popuphiding outercontext", "popuphidden outercontext", + "DOMMenuInactive innercontext", + "DOMMenuItemInactive outercontextmenu", "DOMMenuItemInactive outercontextmenu", + "DOMMenuInactive outercontext" ], + test: () => $("outercontext").hidePopup(), + result: function (testname) { + is($("outermain").triggerNode, $("popuparea"), testname + " outer"); + is($("innermain").triggerNode, $("popuparea"), testname + " inner"); + is($("outercontext").triggerNode, null, testname + " outer context"); + is($("innercontext").triggerNode, null, testname + " inner context"); + is(document.popupNode, $("popuparea"), testname + " document.popupNode"); + } +}, +{ + testname: "hide menus", + events: [ "popuphiding innermain", "popuphidden innermain", + "popuphiding outermain", "popuphidden outermain", + "DOMMenuInactive innermain", + "DOMMenuItemInactive outermenu", "DOMMenuItemInactive outermenu", + "DOMMenuInactive outermain" ], + + test: () => $("outermain").hidePopup(), + result: function (testname) { + is($("outermain").triggerNode, null, testname + " outer"); + is($("innermain").triggerNode, null, testname + " inner"); + is($("outercontext").triggerNode, null, testname + " outer context"); + is($("innercontext").triggerNode, null, testname + " inner context"); + is(document.popupNode, null, testname + " document.popupNode"); + } +} +]; + +SimpleTest.waitForFocus(function runTest() { + return startPopupTests(popupTests); +}); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"><p id="display"/></body> + +</window> diff --git a/toolkit/content/tests/widgets/test_editor_currentURI.xul b/toolkit/content/tests/widgets/test_editor_currentURI.xul new file mode 100644 index 0000000000..20ab3af7cf --- /dev/null +++ b/toolkit/content/tests/widgets/test_editor_currentURI.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" + type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + title="Editor currentURI Tests" onload="runTest();"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <body xmlns="http://www.w3.org/1999/xhtml"> + <p/> + <editor xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + id="editor" + type="content" + editortype="html" + style="width: 400px; height: 100px;"/> + <p/> + <pre id="test"> + </pre> + </body> + <script class="testbody" type="application/javascript"> + <![CDATA[ + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + var editor = document.getElementById("editor"); + // Check that currentURI is a property of editor. + var result = "currentURI" in editor; + is(result, true, "currentURI is a property of editor"); + is(editor.currentURI.spec, "about:blank", "currentURI.spec is about:blank"); + SimpleTest.finish(); + } +]]> +</script> +</window> diff --git a/toolkit/content/tests/widgets/test_menubar.xul b/toolkit/content/tests/widgets/test_menubar.xul new file mode 100644 index 0000000000..7aa15fb2ac --- /dev/null +++ b/toolkit/content/tests/widgets/test_menubar.xul @@ -0,0 +1,30 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Menubar Popup Tests" + onload="setTimeout(runTest, 0);" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <title>Menubar Popup Tests</title> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +SimpleTest.waitForExplicitFinish(); +function runTest() +{ + window.open("window_menubar.xul", "_blank", "width=600,height=600"); +} +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +</window> diff --git a/toolkit/content/tests/widgets/test_mousecapture_area.html b/toolkit/content/tests/widgets/test_mousecapture_area.html new file mode 100644 index 0000000000..532f41a5ac --- /dev/null +++ b/toolkit/content/tests/widgets/test_mousecapture_area.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Mouse capture on area elements tests</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <!-- The border="0" on the images is needed so that when we use + synthesizeMouse we don't accidentally target the border of the image and + miss the area because synthesizeMouse gets the rect of the primary frame + of the target (the area), which is the image due to bug 135040, which + includes the border, but the events targetted at the border aren't + targeted at the area. --> + + <!-- 20x20 of red --> + <img id="image" border="0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + usemap="#Map"/> + + <map name="Map"> + <!-- area over the whole image --> + <area id="area" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" + shape="poly" coords="0,0, 0,20, 20,20, 20,0" href="javascript:void(0);"/> + </map> + + + <!-- 20x20 of red --> + <img id="img1" border="0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + usemap="#sharedMap"/> + + <!-- 20x20 of red --> + <img id="img2" border="0" + src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC" + usemap="#sharedMap"/> + + <map name="sharedMap"> + <!-- area over the whole image --> + <area id="sharedarea" onmousedown="this.setCapture();" onmouseup="this.releaseCapture();" + shape="poly" coords="0,0, 0,20, 20,20, 20,0" href="javascript:void(0);"/> + </map> + + + <div id="otherelement" style="width: 100px; height: 100px;"></div> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.expectAssertions(3); + +SimpleTest.waitForExplicitFinish(); + +function runTests() +{ + // XXX We send a useless click to each image to force it to setup its image + // map, because flushing layout won't do it. Hopefully bug 135040 will make + // this not suck. + synthesizeMouse($("image"), 5, 5, { type: "mousedown" }); + synthesizeMouse($("image"), 5, 5, { type: "mouseup" }); + synthesizeMouse($("img1"), 5, 5, { type: "mousedown" }); + synthesizeMouse($("img1"), 5, 5, { type: "mouseup" }); + synthesizeMouse($("img2"), 5, 5, { type: "mousedown" }); + synthesizeMouse($("img2"), 5, 5, { type: "mouseup" }); + + + // test that setCapture works on an area element (bug 517737) + var area = document.getElementById("area"); + synthesizeMouse(area, 5, 5, { type: "mousedown" }); + synthesizeMouseExpectEvent($("otherelement"), 5, 5, { type: "mousemove" }, + area, "mousemove", "setCapture works on areas"); + synthesizeMouse(area, 5, 5, { type: "mouseup" }); + + // test that setCapture works on an area element when it is part of an image + // map that is used by two images + + var img1 = document.getElementById("img1"); + var sharedarea = document.getElementById("sharedarea"); + // synthesizeMouse just sends the event by coordinates, so this is really a click on the area + synthesizeMouse(img1, 5, 5, { type: "mousedown" }); + synthesizeMouseExpectEvent($("otherelement"), 5, 5, { type: "mousemove" }, + sharedarea, "mousemove", "setCapture works on areas with multiple images"); + synthesizeMouse(img1, 5, 5, { type: "mouseup" }); + + var img2 = document.getElementById("img2"); + // synthesizeMouse just sends the event by coordinates, so this is really a click on the area + synthesizeMouse(img2, 5, 5, { type: "mousedown" }); + synthesizeMouseExpectEvent($("otherelement"), 5, 5, { type: "mousemove" }, + sharedarea, "mousemove", "setCapture works on areas with multiple images"); + synthesizeMouse(img2, 5, 5, { type: "mouseup" }); + + // Bug 862673 - nuke all content so assertions in this test are attributed to + // this test rather than the one which happens to follow. + var content = document.getElementById("content"); + content.parentNode.removeChild(content); + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(runTests); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_popupanchor.xul b/toolkit/content/tests/widgets/test_popupanchor.xul new file mode 100644 index 0000000000..814d9272f3 --- /dev/null +++ b/toolkit/content/tests/widgets/test_popupanchor.xul @@ -0,0 +1,430 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Anchor Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <panel id="testPanel" + type="arrow" + animate="false" + noautohide="true"> + </panel> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +<![CDATA[ +var anchor, panel, arrow; + +function is_close(got, exp, msg) { + // on some platforms we see differences of a fraction of a pixel - so + // allow any difference of < 1 pixels as being OK. + ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal(-ish) to " + exp); +} + +function isArrowPositionedOn(side, offset) { + var arrowRect = arrow.getBoundingClientRect(); + var arrowMidX = (arrowRect.left + arrowRect.right) / 2; + var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; + var panelRect = panel.getBoundingClientRect(); + var panelMidX = (panelRect.left + panelRect.right) / 2; + var panelMidY = (panelRect.top + panelRect.bottom) / 2; + // First check the "flip" of the panel is correct. If we are expecting the + // arrow to be pointing to the left side of the anchor, the arrow must + // also be on the left side of the panel (and vice-versa) + // XXX - on OSX, the arrow seems to always be exactly in the center, hence + // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... + switch (side) { + case "left": + ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); + break; + case "right": + ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); + break; + case "top": + ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); + break; + case "bottom": + ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); + break; + default: + ok(false, "invalid position " + where); + break; + } + // Now check the arrow really is pointing where we expect. The middle of + // the arrow should be pointing exactly to the left (or right) side of the + // anchor rect, +- any offsets. + if (offset === null) // special case - explicit 'null' means 'don't check offset' + return; + offset = offset || 0; // no param means no offset expected. + var anchorRect = anchor.getBoundingClientRect(); + var anchorPos = anchorRect[side]; + switch (side) { + case "left": + case "right": + is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); + break; + case "top": + case "bottom": + is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); + break; + default: + ok(false, "unknown side " + side); + break; + } +} + +function openSlidingPopup(position, callback) { + panel.setAttribute("flip", "slide"); + _openPopup(position, callback); +} + +function openPopup(position, callback) { + panel.setAttribute("flip", "both"); + _openPopup(position, callback); +} + +function waitForPopupPositioned(actionFn, callback) +{ + panel.addEventListener("popuppositioned", function listener() { + panel.removeEventListener("popuppositioned", listener, false); + callback(); + }, false); + actionFn(); +} + +function _openPopup(position, callback) { + // this is very ugly: the panel CSS sets the arrow's list-style-image based + // on the 'side' attribute. If the setting of the 'side' attribute causes + // the image to change, we may get the popupshown event before the new + // image has loaded - which causes the size of the arrow to be incorrect + // for a brief moment - right when we are measuring it! + // So we work around this in 2 steps: + // * Force the 'side' attribute to a value which causes the CSS to not + // specify an image - then when the popup gets shown, the correct image + // is set, causing a load() event on the image element. + // * Listen to *both* popupshown and the image load event. When both have + // fired (the order is indeterminate) we start the test. + panel.setAttribute("side", "noside"); + var numEvents = 0; + function onEvent() { + if (++numEvents == 2) // after both panel 'popupshown' and image 'load' + callback(); + }; + panel.addEventListener("popupshown", function popupshown() { + panel.removeEventListener("popupshown", popupshown); + onEvent(); + }); + arrow.addEventListener("load", function imageload() { + arrow.removeEventListener("load", imageload); + onEvent(); + }); + panel.openPopup(anchor, position); +} + +var tests = [ + // A panel with the anchor after_end - the anchor should not move on resize + ['simpleResizeHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + next(); + }); + }], + + ['simpleResizeVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("bottom"); // should not have flipped + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("bottom"); // should not have flipped + next(); + }); + }], + + ['flippingResizeHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); + isArrowPositionedOn("left"); // check it flipped and has zero offset. + next(); + }); + }], + + ['flippingResizeVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); + isArrowPositionedOn("top"); // check it flipped and has zero offset. + next(); + }); + }], + + ['simpleMoveToAnchorHorizontal', 'middle', function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.moveToAnchor(anchor, "after_end", 20, 0); + // the anchor and the panel should have moved 20px right without flipping. + isArrowPositionedOn("right", 20); + panel.moveToAnchor(anchor, "after_end", -20, 0); + // the anchor and the panel should have moved 20px left without flipping. + isArrowPositionedOn("right", -20); + next(); + }); + }], + + ['simpleMoveToAnchorVertical', 'middle', function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.moveToAnchor(anchor, "start_after", 0, 20); + // the anchor and the panel should have moved 20px down without flipping. + isArrowPositionedOn("bottom", 20); + panel.moveToAnchor(anchor, "start_after", 0, -20); + // the anchor and the panel should have moved 20px up without flipping. + isArrowPositionedOn("bottom", -20); + next(); + }); + }], + + // Do a moveToAnchor that causes the panel to flip horizontally + ['flippingMoveToAnchorHorizontal', 'middle', function(next) { + var anchorRight = anchor.getBoundingClientRect().right; + // Size the panel such that it only just fits from the left-hand side of + // the window to the right of the anchor - thus, it will fit when + // anchored to the right-hand side of the anchor. + panel.sizeTo(anchorRight - 10, 100); + openPopup("after_end", function() { + isArrowPositionedOn("right"); + // Ask for it to be anchored 1/2 way between the left edge of the window + // and the anchor right - it can't fit with the panel on the left/arrow + // on the right, so it must flip (arrow on the left, panel on the right) + var offset = Math.floor(-anchorRight / 2); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", offset, 0), + () => { + isArrowPositionedOn("left", offset); // should have flipped and have the offset. + // resize back to original and move to a zero offset - it should flip back. + + panel.sizeTo(anchorRight - 10, 100); + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "after_end", 0, 0), + () => { + isArrowPositionedOn("right"); // should have flipped back and no offset + next(); + }); + }); + }); + }], + + // Do a moveToAnchor that causes the panel to flip vertically + ['flippingMoveToAnchorVertical', 'middle', function(next) { + var anchorBottom = anchor.getBoundingClientRect().bottom; + // See comments above in flippingMoveToAnchorHorizontal, but read + // "top/bottom" instead of "left/right" + panel.sizeTo(100, anchorBottom - 10); + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var offset = Math.floor(-anchorBottom / 2); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, offset), + () => { + isArrowPositionedOn("top", offset); + panel.sizeTo(100, anchorBottom - 10); + + waitForPopupPositioned( + () => panel.moveToAnchor(anchor, "start_after", 0, 0), + () => { + isArrowPositionedOn("bottom"); + next(); + }); + }); + }); + }], + + ['veryWidePanel-after_end', 'middle', function(next) { + openSlidingPopup("after_end", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit, + // meaning the arrow should "slide" down the panel. + panel.sizeTo(window.innerWidth - 10, 60); + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryWidePanel-before_start', 'middle', function(next) { + openSlidingPopup("before_start", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now size it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + panel.sizeTo(window.innerWidth - 10, 60); + is(panel.getBoundingClientRect().width, window.innerWidth - 10, "width is what we requested") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryTallPanel-start_after', 'middle', function(next) { + openSlidingPopup("start_after", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit, + // meaning the arrow should "slide" down the panel. + panel.sizeTo(100, window.innerHeight - 10); + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + ['veryTallPanel-start_before', 'middle', function(next) { + openSlidingPopup("start_before", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now size it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + panel.sizeTo(100, window.innerHeight - 10); + is(panel.getBoundingClientRect().height, window.innerHeight - 10, "height is what we requested") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + is_close(curArrowRect.left, origArrowRect.left, "arrow should not have moved"); + is_close(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + }], + + // Tests against the anchor at the right-hand side of the window + ['afterend', 'right', function(next) { + openPopup("after_end", function() { + // when we request too far to the right/bottom, the panel gets shrunk + // and moved. The amount it is shrunk by is how far it is moved. + var panelRect = panel.getBoundingClientRect(); + // panel was requested 100px wide - calc offset based on actual width. + var offset = panelRect.width - 100; + isArrowPositionedOn("right", offset); + next(); + }); + }], + + ['after_start', 'right', function(next) { + openPopup("after_start", function() { + // See above - we are still too far to the right, but the anchor is + // on the other side. + var panelRect = panel.getBoundingClientRect(); + var offset = panelRect.width - 100; + isArrowPositionedOn("right", offset); + next(); + }); + }], + + // Tests against the anchor at the left-hand side of the window + ['after_start', 'left', function(next) { + openPopup("after_start", function() { + var panelRect = panel.getBoundingClientRect(); + is(panelRect.left, 0, "panel remains within the screen"); + // not sure how to determine the offset here, so given we have checked + // the panel is as left as possible while still being inside the window, + // we just don't check the offset. + isArrowPositionedOn("left", null); + next(); + }); + }], +] + +function runTests() { + function runNextTest() { + let result = tests.shift(); + if (!result) { + // out of tests + panel.hidePopup(); + SimpleTest.finish(); + return; + } + let [name, anchorPos, test] = result; + SimpleTest.info("sub-test " + anchorPos + "." + name + " starting"); + // first arrange for the anchor to be where the test requires it. + panel.hidePopup(); + panel.sizeTo(100, 50); + // hide all the anchors here, then later we make one of them visible. + document.getElementById("anchor-left-wrapper").style.display = "none"; + document.getElementById("anchor-middle-wrapper").style.display = "none"; + document.getElementById("anchor-right-wrapper").style.display = "none"; + switch(anchorPos) { + case 'middle': + anchor = document.getElementById("anchor-middle"); + document.getElementById("anchor-middle-wrapper").style.display = "block"; + break; + case 'left': + anchor = document.getElementById("anchor-left"); + document.getElementById("anchor-left-wrapper").style.display = "block"; + break; + case 'right': + anchor = document.getElementById("anchor-right"); + document.getElementById("anchor-right-wrapper").style.display = "block"; + break; + default: + SimpleTest.ok(false, "Bad anchorPos: " + anchorPos); + runNextTest(); + return; + } + try { + test(runNextTest); + } catch (ex) { + SimpleTest.ok(false, "sub-test " + anchorPos + "." + name + " failed: " + ex.toString() + "\n" + ex.stack); + runNextTest(); + } + } + runNextTest(); +} + +SimpleTest.waitForExplicitFinish(); + +addEventListener("load", function() { + // anchor is set by the test runner above + panel = document.getElementById("testPanel"); + + arrow = SpecialPowers.wrap(document).getAnonymousElementByAttribute(panel, "anonid", "arrow"); + runTests(); +}); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<!-- Our tests assume at least 100px around the anchor on all sides, else the + panel may flip when we don't expect it to +--> +<div id="anchor-middle-wrapper" style="margin: 100px 100px 100px 100px;"> + <p>The anchor --> <span id="anchor-middle">v</span> <--</p> +</div> +<div id="anchor-left-wrapper" style="text-align: left; display: none;"> + <p><span id="anchor-left">v</span> <-- The anchor;</p> +</div> +<div id="anchor-right-wrapper" style="text-align: right; display: none;"> + <p>The anchor --> <span id="anchor-right">v</span></p> +</div> +</body> + +</window> diff --git a/toolkit/content/tests/widgets/test_popupreflows.xul b/toolkit/content/tests/widgets/test_popupreflows.xul new file mode 100644 index 0000000000..6969f77672 --- /dev/null +++ b/toolkit/content/tests/widgets/test_popupreflows.xul @@ -0,0 +1,111 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Reflow Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <panel id="testPanel" + type="arrow" + noautohide="true"> + </panel> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<script> +<![CDATA[ +var {classes: Cc, interfaces: Ci, utils: Cu} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Promise.jsm"); + +let panel, anchor, arrow; + +// A reflow observer - it just remembers the stack trace of all sync reflows +// done by the panel. +let observer = { + reflows: [], + reflow: function (start, end) { + // Ignore reflows triggered by native code + // (Reflows from native code only have an empty stack after the first frame) + var path = (new Error().stack).split("\n").slice(1).join(""); + if (path === "") { + return; + } + + this.reflows.push(new Error().stack); + }, + + reflowInterruptible: function (start, end) { + // We're not interested in interruptible reflows. Why, you ask? Because + // we've simply cargo-culted this test from browser_tabopen_reflows.js! + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, + Ci.nsISupportsWeakReference]) +}; + +// A test utility that counts the reflows caused by a test function. If the +// count of reflows isn't what is expected, it causes a test failure and logs +// the stack trace of all seen reflows. +function countReflows(testfn, expected) { + let deferred = Promise.defer(); + observer.reflows = []; + let docShell = panel.ownerDocument.defaultView + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsIDocShell); + docShell.addWeakReflowObserver(observer); + testfn().then(() => { + docShell.removeWeakReflowObserver(observer); + SimpleTest.is(observer.reflows.length, expected, "correct number of reflows"); + if (observer.reflows.length != expected) { + SimpleTest.info("stack traces of reflows:\n" + observer.reflows.join("\n") + "\n"); + } + deferred.resolve(); + }); + return deferred.promise +} + +function openPopup() { + let deferred = Promise.defer(); + panel.addEventListener("popupshown", function popupshown() { + panel.removeEventListener("popupshown", popupshown); + deferred.resolve(); + }); + panel.openPopup(anchor, "before_start"); + return deferred.promise +} + +// ******************** +// The actual tests... +// We only have one atm - simply open a popup. +// +function testSimplePanel() { + return openPopup(); +} + +// ******************** +// The test harness... +// +SimpleTest.waitForExplicitFinish(); + +addEventListener("load", function() { + anchor = document.getElementById("anchor"); + panel = document.getElementById("testPanel"); + arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow"); + + // Cancel the arrow panel slide-in transition (bug 767133) - we are only + // testing reflows in the core panel implementation and not reflows that may + // or may not be caused by transitioning.... + arrow.style.transition = "none"; + + // and off we go... + countReflows(testSimplePanel, 1).then(SimpleTest.finish); +}); +]]> +</script> +<body xmlns="http://www.w3.org/1999/xhtml"> + <p>The anchor --> <span id="anchor">v</span> <--</p> +</body> +</window> diff --git a/toolkit/content/tests/widgets/test_tree_column_reorder.xul b/toolkit/content/tests/widgets/test_tree_column_reorder.xul new file mode 100644 index 0000000000..5315fee439 --- /dev/null +++ b/toolkit/content/tests/widgets/test_tree_column_reorder.xul @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- + XUL Widget Test for reordering tree columns + --> +<window title="Tree" width="500" height="600" + onload="setTimeout(testtag_tree_column_reorder, 0);" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> + +<script src="tree_shared.js"/> + +<tree id="tree-column-reorder" rows="1" enableColumnDrag="true"> + <treecols> + <treecol id="col_0" label="col_0" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_1" label="col_1" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_2" label="col_2" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_3" label="col_3" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_4" label="col_4" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_5" label="col_5" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_6" label="col_6" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_7" label="col_7" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_8" label="col_8" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_9" label="col_9" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_10" label="col_10" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_11" label="col_11" flex="1"/> + <splitter class="tree-splitter"/> + <treecol id="col_12" label="col_12" flex="1"/> + </treecols> + <treechildren id="treechildren-column-reorder"> + <treeitem> + <treerow> + <treecell label="col_0"/> + <treecell label="col_1"/> + <treecell label="col_2"/> + <treecell label="col_3"/> + <treecell label="col_4"/> + <treecell label="col_5"/> + <treecell label="col_6"/> + <treecell label="col_7"/> + <treecell label="col_8"/> + <treecell label="col_9"/> + <treecell label="col_10"/> + <treecell label="col_11"/> + <treecell label="col_12"/> + </treerow> + </treeitem> + </treechildren> +</tree> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +SimpleTest.waitForExplicitFinish(); + +]]> +</script> + +</window> + diff --git a/toolkit/content/tests/widgets/test_videocontrols.html b/toolkit/content/tests/widgets/test_videocontrols.html new file mode 100644 index 0000000000..191aaef580 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols.html @@ -0,0 +1,411 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video width="320" height="240" id="video" controls mozNoDynamicControls preload="auto"></video> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/* + * Positions of the UI elements, relative to the upper-left corner of the + * <video> box. + */ +const videoWidth = 320; +const videoHeight = 240; +const videoDuration = 3.8329999446868896; + +const playButtonWidth = 28; +const playButtonHeight = 28; +const muteButtonWidth = 33; +const muteButtonHeight = 28; +const durationWidth = 34; +const fullscreenButtonWidth = 28; +const fullscreenButtonHeight = 28; +const volumeSliderWidth = 32; +const scrubberWidth = videoWidth - playButtonWidth - durationWidth - muteButtonWidth - volumeSliderWidth - fullscreenButtonWidth; +const scrubberHeight = 28; + +// Play button is on the bottom-left +const playButtonCenterX = 0 + Math.round(playButtonWidth / 2); +const playButtonCenterY = videoHeight - Math.round(playButtonHeight / 2); +// Mute button is on the bottom-right before the full screen button and volume slider +const muteButtonCenterX = videoWidth - Math.round(muteButtonWidth / 2) - volumeSliderWidth - fullscreenButtonWidth; +const muteButtonCenterY = videoHeight - Math.round(muteButtonHeight / 2); +// Fullscreen button is on the bottom-right at the far end +const fullscreenButtonCenterX = videoWidth - Math.round(fullscreenButtonWidth / 2); +const fullscreenButtonCenterY = videoHeight - Math.round(fullscreenButtonHeight / 2); +// Scrubber bar is between the play and mute buttons. We don't need it's +// X center, just the offset of its box. +const scrubberOffsetX = 0 + playButtonWidth; +const scrubberCenterY = videoHeight - Math.round(scrubberHeight / 2); + +var testnum = 1; +var video = document.getElementById("video"); + +const domUtil = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"] + .getService(SpecialPowers.Ci.inIDOMUtils); + +function getButtonByAttribute(aName, aValue) { + var kids = domUtil.getChildrenForNode(video, true); + var videocontrols = kids[1]; + return SpecialPowers.wrap(document) + .getAnonymousElementByAttribute(videocontrols, aName, aValue); +} + +function isMuteButtonMuted() { + var muteButton = getButtonByAttribute('class', 'muteButton'); + return muteButton.getAttribute('muted') === 'true'; +} + +function isVolumeSliderShowingCorrectVolume(expectedVolume) { + var volumeButton = getButtonByAttribute('anonid', 'volumeForeground'); + let expectedPaddingRight = (1 - expectedVolume) * volumeSliderWidth + "px"; + is(volumeButton.style.paddingRight, expectedPaddingRight, + "volume slider should match expected volume"); +} + +function forceReframe() { + // Setting display then getting the bounding rect to force a frame + // reconstruction on the video element. + video.style.display = "block"; + video.getBoundingClientRect(); + video.style.display = ""; + video.getBoundingClientRect(); +} + +function runTest(event) { + ok(true, "----- test #" + testnum + " -----"); + + switch (testnum) { + /* + * Check operation of play/pause/mute/unmute buttons. + */ + case 1: + // Check initial state upon load + is(event.type, "canplaythrough", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + // Click the play button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { }); + }); + break; + + case 2: + is(event.type, "play", "checking event type"); + is(video.paused, false, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + // Click the pause button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { }); + }); + break; + + case 3: + is(event.type, "pause", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Mute. + }); + break; + + case 4: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, true, "checking video mute state"); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); // Unmute. + }); + break; + + /* + * Bug 470596: Make sure that having CSS border or padding doesn't + * break the controls (though it should move them) + */ + case 5: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + video.style.border = "medium solid purple"; + video.style.borderWidth = "30px 40px 50px 60px"; + video.style.padding = "10px 20px 30px 40px"; + // totals: top: 40px, right: 60px, bottom: 80px, left: 100px + + // Click the play button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, 100 + playButtonCenterX, 40 + playButtonCenterY, { }); + }); + break; + + case 6: + is(event.type, "play", "checking event type"); + is(video.paused, false, "checking video play state"); + is(video.muted, false, "checking video mute state"); + video.pause(); + break; + + case 7: + is(event.type, "pause", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + + // Click the mute button + SimpleTest.executeSoon(() => { + synthesizeMouse(video, 100 + muteButtonCenterX, 40 + muteButtonCenterY, { }); + }); + break; + + case 8: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, true, "checking video mute state"); + // Clear the style set in test 5. + video.style.border = ""; + video.style.borderWidth = ""; + video.style.padding = ""; + + video.muted = false; + break; + + /* + * Previous tests have moved playback postion, reset it to 0. + */ + case 9: + is(event.type, "volumechange", "checking event type"); + is(video.paused, true, "checking video play state"); + is(video.muted, false, "checking video mute state"); + ok(true, "video position is at " + video.currentTime); + video.currentTime = 0.0; + break; + + case 10: + is(event.type, "seeking", "checking event type"); + ok(true, "video position is at " + video.currentTime); + break; + + /* + * Drag the slider's thumb to the halfway point with the mouse. + */ + case 11: + is(event.type, "seeked", "checking event type"); + ok(true, "video position is at " + video.currentTime); + // Bug 477434 -- sometimes we get 0.098999 here instead of 0! + // is(video.currentTime, 0.0, "checking playback position"); + + SimpleTest.executeSoon(() => { + var beginDragX = scrubberOffsetX; + var endDragX = scrubberOffsetX + (scrubberWidth / 2); + synthesizeMouse(video, beginDragX, scrubberCenterY, { type: "mousedown", button: 0 }); + synthesizeMouse(video, endDragX, scrubberCenterY, { type: "mousemove", button: 0 }); + synthesizeMouse(video, endDragX, scrubberCenterY, { type: "mouseup", button: 0 }); + }); + break; + + case 12: + is(event.type, "seeking", "checking event type"); + ok(true, "video position is at " + video.currentTime); + break; + + /* + * Click the slider at the 1/4 point with the mouse (jump backwards) + */ + case 13: + is(event.type, "seeked", "checking event type"); + ok(true, "video position is at " + video.currentTime); + var expectedTime = videoDuration / 2; + ok(Math.abs(video.currentTime - expectedTime) < 0.1, "checking expected playback position"); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, { }); + }); + break; + + case 14: + is(event.type, "seeking", "checking event type"); + ok(true, "video position is at " + video.currentTime); + break; + + case 15: + is(event.type, "seeked", "checking event type"); + ok(true, "video position is at " + video.currentTime); + // The scrubber currently just jumps towards the nearest pageIncrement point, not + // precisely to the point clicked. So, expectedTime isn't (videoDuration / 4). + // We should end up at 1.733, but sometimes we end up at 1.498. I guess + // it's timing depending if the <scale> things it's click-and-hold, or a + // single click. So, just make sure we end up less that the previous + // position. + lastPosition = (videoDuration / 2) - 0.1; + ok(video.currentTime < lastPosition, "checking expected playback position"); + + // Set volume to 0.1 so one down arrow hit will decrease it to 0. + video.volume = 0.1; + break; + + // See bug 694696. + case 16: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.1, "Volume should be set."); + ok(!video.muted, "Video is not muted."); + + video.focus(); + SimpleTest.executeSoon(() => synthesizeKey("VK_DOWN", {})); + break; + + case 17: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0, "Volume should be 0."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(isMuteButtonMuted(), "Mute button says it's muted"); + synthesizeKey("VK_UP", {}); + }); + break; + + case 18: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.1, "Volume is increased."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(!isMuteButtonMuted(), "Mute button says it's not muted"); + synthesizeKey("VK_DOWN", {}); + }); + break; + + case 19: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0, "Volume should be 0."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(isMuteButtonMuted(), "Mute button says it's muted"); + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); + }); + break; + + case 20: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.5, "Volume should be 0.5."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => synthesizeKey("VK_UP", {})); + break; + + case 21: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.6, "Volume should be 0.6."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); + }); + break; + + case 22: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.6, "Volume should be 0.6."); + ok(video.muted, "Video is muted."); + + SimpleTest.executeSoon(() => { + ok(isMuteButtonMuted(), "Mute button says it's muted"); + synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, { }); + }); + break; + + case 23: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.6, "Volume should be 0.6."); + ok(!video.muted, "Video is not muted."); + + SimpleTest.executeSoon(() => { + ok(!isMuteButtonMuted(), "Mute button says it's not muted"); + synthesizeMouse(video, fullscreenButtonCenterX, fullscreenButtonCenterY, { }); + }); + break; + + case 24: + is(event.type, "mozfullscreenchange", "checking event type"); + is(video.volume, 0.6, "Volume should still be 0.6"); + SimpleTest.executeSoon(function() { + isVolumeSliderShowingCorrectVolume(video.volume); + synthesizeKey("VK_ESCAPE", {}); + }); + break; + + case 25: + is(event.type, "mozfullscreenchange", "checking event type"); + is(video.volume, 0.6, "Volume should still be 0.6"); + SimpleTest.executeSoon(function() { + isVolumeSliderShowingCorrectVolume(video.volume); + forceReframe(); + video.focus(); + synthesizeKey("VK_DOWN", {}); + }); + break; + + case 26: + is(event.type, "volumechange", "checking event type"); + is(video.volume, 0.5, "Volume should be decreased by 0.1"); + SimpleTest.executeSoon(function() { + isVolumeSliderShowingCorrectVolume(video.volume); + SimpleTest.finish(); + }); + break; + + default: + throw "unexpected test #" + testnum + " w/ event " + event.type; + } + + testnum++; +} + + + +function canplaythroughevent(event) { + video.removeEventListener("canplaythrough", canplaythroughevent, false); + // Other events expected by the test. + video.addEventListener("play", runTest, false); + video.addEventListener("pause", runTest, false); + video.addEventListener("volumechange", runTest, false); + video.addEventListener("seeking", runTest, false); + video.addEventListener("seeked", runTest, false); + document.addEventListener("mozfullscreenchange", runTest, false); + // Begin the test. + runTest(event); +} + +function startMediaLoad() { + // Kick off test once video has loaded, in its canplaythrough event handler. + video.src = "seek_with_sound.ogg"; + video.addEventListener("canplaythrough", canplaythroughevent, false); +} + +function loadevent(event) { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad); +} + +window.addEventListener("load", loadevent, false); + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_audio.html b/toolkit/content/tests/widgets/test_videocontrols_audio.html new file mode 100644 index 0000000000..7d1dc32e39 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_audio.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls with Audio file test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video id="video" controls preload="metadata"></video> +</div> + +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.7"> + + var domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + + function findElementByAttribute(element, aName, aValue) { + if (!('getAttribute' in element)) { + return false; + } + if (element.getAttribute(aName) === aValue) { + return element; + } + let children = domUtils.getChildrenForNode(element, true); + for (let child of children) { + var result = findElementByAttribute(child, aName, aValue); + if (result) { + return result; + } + } + return false; + } + + function loadedmetadata(event) { + SimpleTest.executeSoon(function() { + var controlBar = findElementByAttribute(video, "class", "controlBar"); + is(controlBar.getAttribute("fullscreen-unavailable"), "true", "Fullscreen button is hidden"); + SimpleTest.finish(); + }); + } + + var video = document.getElementById("video"); + + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest); + function startTest() { + // Kick off test once audio has loaded. + video.addEventListener("loadedmetadata", loadedmetadata, false); + video.src = "audio.ogg"; + } + + SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_audio_direction.html b/toolkit/content/tests/widgets/test_videocontrols_audio_direction.html new file mode 100644 index 0000000000..2512b997a0 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_audio_direction.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls directionality test</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var tests = [ + {op: "==", test: "videocontrols_direction-2a.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2b.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2c.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2d.html", ref: "videocontrols_direction-2-ref.html"}, + {op: "==", test: "videocontrols_direction-2e.html", ref: "videocontrols_direction-2-ref.html"} +]; + +</script> +<script type="text/javascript" src="videocontrols_direction_test.js"></script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html b/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html new file mode 100644 index 0000000000..6391dcc1b7 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_iframe_fullscreen.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test - iframe</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> +<iframe id="ifr1"></iframe> +<iframe id="ifr2" allowfullscreen></iframe> +</div> + +<pre id="test"> +<script clas="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + const domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + const iframe1 = SpecialPowers.wrap(document.getElementById("ifr1")); + const iframe2 = SpecialPowers.wrap(document.getElementById("ifr2")); + const testCases = []; + + function checkIframeFullscreenAvailable(ifr) { + const available = ifr.hasAttribute("allowfullscreen"); + let video; + + return () => new Promise(resolve => { + ifr.srcdoc = `<video id="video" controls preload="auto"></video>`; + ifr.addEventListener("load", resolve, false); + }).then(() => new Promise(resolve => { + video = ifr.contentDocument.getElementById("video"); + video.src = "seek_with_sound.ogg"; + video.addEventListener("loadedmetadata", resolve, false); + })).then(() => new Promise(resolve => { + const videoControl = domUtils.getChildrenForNode(video, true)[1]; + const controlBar = video.ownerDocument.getAnonymousElementByAttribute( + videoControl, "class", "controlBar"); + + is(controlBar.getAttribute("fullscreen-unavailable") == "true", !available, "The controlbar should have an attribute marking whether fullscreen is available that corresponds to if the iframe has the allowfullscreen attribute."); + resolve(); + })); + } + + function start() { + testCases.reduce((promise, task) => promise.then(task), Promise.resolve()); + } + + function load() { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, start); + } + + testCases.push(checkIframeFullscreenAvailable(iframe1)); + testCases.push(checkIframeFullscreenAvailable(iframe2)); + testCases.push(SimpleTest.finish); + + window.addEventListener("load", load, false); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_jsdisabled.html b/toolkit/content/tests/widgets/test_videocontrols_jsdisabled.html new file mode 100644 index 0000000000..f57cda0636 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_jsdisabled.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +function runTest(event) { + info(true, "----- test #" + testnum + " -----"); + + switch (testnum) { + case 1: + is(event.type, "timeupdate", "checking event type"); + is(video.paused, false, "checking video play state"); + video.removeEventListener("timeupdate", runTest); + + // Click to toggle play/pause (now pausing) + synthesizeMouseAtCenter(video, {}, win); + break; + + case 2: + is(event.type, "pause", "checking event type"); + is(video.paused, true, "checking video play state"); + win.close(); + + SimpleTest.finish(); + break; + + default: + ok(false, "unexpected test #" + testnum + " w/ event " + event.type); + throw "unexpected test #" + testnum + " w/ event " + event.type; + } + + testnum++; +} + +SpecialPowers.pushPrefEnv({"set": [["javascript.enabled", false]]}, startTest); + +var testnum = 1; + +var video; +function loadevent(event) { + is(win["testExpando"], undefined, "expando shouldn't exist because js is disabled"); + video = win.document.querySelector("video"); + // Other events expected by the test. + video.addEventListener("timeupdate", runTest, false); + video.addEventListener("pause", runTest, false); +} + +var win; +function startTest() { + var videoURL = new URL("seek_with_sound.ogg", document.documentURI).href; + var url = "data:text/html,<video src=" + videoURL + " controls autoplay=true></video><script>window.testExpando = true;</scr" + "ipt>"; + + win = window.open(url); + win.addEventListener("load", loadevent, false); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html b/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html new file mode 100644 index 0000000000..d681b31587 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_onclickplay.html @@ -0,0 +1,74 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video id="video" controls mozNoDynamicControls preload="auto"></video> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +var video = document.getElementById("video"); + +function startMediaLoad() { + // Kick off test once video has loaded, in its canplaythrough event handler. + video.src = "seek_with_sound.ogg"; + video.addEventListener("canplaythrough", runTest, false); +} + +function loadevent(event) { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad); +} + +window.addEventListener("load", loadevent, false); + +function runTest() { + video.addEventListener("click", function() { + this.play(); + }); + ok(video.paused, "video should be paused initially"); + + new Promise(resolve => { + let timeupdates = 0; + video.addEventListener("timeupdate", function timeupdate() { + ok(!video.paused, "video should not get paused after clicking in middle"); + + if (++timeupdates == 3) { + video.removeEventListener("timeupdate", timeupdate); + resolve(); + } + }); + + synthesizeMouseAtCenter(video, {}, window); + }).then(function() { + new Promise(resolve => { + video.addEventListener("pause", function onpause() { + setTimeout(() => { + ok(video.paused, "video should still be paused 200ms after pause request"); + // When the video reaches the end of playback it is automatically paused. + // Check during the pause event that the video has not reachd the end + // of playback. + ok(!video.ended, "video should not have paused due to playback ending"); + resolve(); + }, 200); + }); + + synthesizeMouse(video, 10, video.clientHeight - 10, {}, window); + }).then(SimpleTest.finish); + }); +} + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_standalone.html b/toolkit/content/tests/widgets/test_videocontrols_standalone.html new file mode 100644 index 0000000000..8d1ce89844 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_standalone.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +const videoWidth = 320; +const videoHeight = 240; + +function getMediaElement(aWindow) { + return aWindow.document.getElementsByTagName("video")[0]; +} + +var popup = window.open("seek_with_sound.ogg"); +popup.addEventListener("load", function onLoad() { + popup.removeEventListener("load", onLoad); + var video = getMediaElement(popup); + if (!video.paused) + runTestVideo(video); + else { + video.addEventListener("play", function onPlay() { + video.removeEventListener("play", onPlay); + runTestVideo(video); + }); + } +}); + +function runTestVideo(aVideo) { + var condition = function() { + var boundingRect = aVideo.getBoundingClientRect(); + return boundingRect.width == videoWidth && + boundingRect.height == videoHeight; + }; + waitForCondition(condition, function() { + var boundingRect = aVideo.getBoundingClientRect(); + is(boundingRect.width, videoWidth, "Width of the video should match expectation"); + is(boundingRect.height, videoHeight, "Height of video should match expectation"); + popup.close(); + runTestAudioPre(); + }, "The media element should eventually be resized to match the intrinsic size of the video."); +} + +function runTestAudioPre() { + popup = window.open("audio.ogg"); + popup.addEventListener("load", function onLoad() { + popup.removeEventListener("load", onLoad); + var audio = getMediaElement(popup); + if (!audio.paused) + runTestAudio(audio); + else { + audio.addEventListener("play", function onPlay() { + audio.removeEventListener("play", onPlay); + runTestAudio(audio); + }) + } + }) +} + +function runTestAudio(aAudio) { + info("User agent (help diagnose bug #943556): " + navigator.userAgent); + var isAndroid = navigator.userAgent.includes("Android"); + var expectedHeight = isAndroid ? 103 : 28; + var condition = function () { + var boundingRect = aAudio.getBoundingClientRect(); + return boundingRect.height == expectedHeight; + }; + waitForCondition(condition, function () { + var boundingRect = aAudio.getBoundingClientRect(); + is(boundingRect.height, expectedHeight, + "Height of audio element should be " + expectedHeight + ", which is equal to the controls bar."); + popup.close(); + SimpleTest.finish(); + }, "The media element should eventually be resized to match the height of the audio controls."); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_video_direction.html b/toolkit/content/tests/widgets/test_videocontrols_video_direction.html new file mode 100644 index 0000000000..54e0d5e722 --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_video_direction.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls directionality test</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> +</div> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +var tests = [ + {op: "==", test: "videocontrols_direction-1a.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1b.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1c.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1d.html", ref: "videocontrols_direction-1-ref.html"}, + {op: "==", test: "videocontrols_direction-1e.html", ref: "videocontrols_direction-1-ref.html"}, +]; + +</script> +<script type="text/javascript" src="videocontrols_direction_test.js"></script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/test_videocontrols_vtt.html b/toolkit/content/tests/widgets/test_videocontrols_vtt.html new file mode 100644 index 0000000000..27052b770e --- /dev/null +++ b/toolkit/content/tests/widgets/test_videocontrols_vtt.html @@ -0,0 +1,133 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Video controls test - VTT</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<p id="display"></p> + +<div id="content"> + <video id="video" controls preload="auto"></video> +</div> + +<pre id="test"> +<script clas="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + const domUtils = SpecialPowers.Cc["@mozilla.org/inspector/dom-utils;1"]. + getService(SpecialPowers.Ci.inIDOMUtils); + const video = document.getElementById("video"); + const ccBtn = getElementByAttribute("class", "closedCaptionButton"); + const ttList = getElementByAttribute("class", "textTrackList"); + const testCases = []; + + testCases.push(() => new Promise(resolve => { + is(ccBtn.getAttribute("hidden"), "true", "CC button should hide"); + + resolve(); + })); + + testCases.push(() => new Promise(resolve => { + video.addTextTrack("descriptions", "English", "en"); + video.addTextTrack("chapters", "English", "en"); + video.addTextTrack("metadata", "English", "en"); + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("hidden"), "true", "CC button should hide if no supported tracks provided"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const sub = video.addTextTrack("subtitles", "English", "en"); + sub.mode = "disabled"; + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("hidden"), "", "CC button should show"); + is(ccBtn.getAttribute("enabled"), "", "CC button should be disabled"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const subtitle = video.addTextTrack("subtitles", "English", "en"); + subtitle.mode = "showing"; + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled"); + subtitle.mode = "disabled"; + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const caption = video.addTextTrack("captions", "English", "en"); + caption.mode = "showing"; + + SimpleTest.executeSoon(() => { + is(ccBtn.getAttribute("enabled"), "true", "CC button should be enabled"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + synthesizeMouseAtCenter(ccBtn, {}); + + SimpleTest.executeSoon(() => { + is(ttList.hasAttribute("hidden"), false, "Texttrack menu should show up"); + is(ttList.lastChild.getAttribute("on"), "true", "The last added item should be highlighted"); + + resolve(); + }); + })); + + testCases.push(() => new Promise(resolve => { + const tt = ttList.children[1]; + + isnot(tt.getAttribute("on"), "true", "Item should be off before click"); + synthesizeMouseAtCenter(tt, {}); + + SimpleTest.executeSoon(() => { + is(tt.getAttribute("on"), "true", "Selected item should be enabled"); + is(ttList.getAttribute("hidden"), "true", "Should hide texttrack menu once clicked on an item"); + + resolve(); + }); + })); + + function executeTestCases(tasks) { + return tasks.reduce((promise, task) => promise.then(task), Promise.resolve()); + } + + function getElementByAttribute(aName, aValue) { + const videoControl = domUtils.getChildrenForNode(video, true)[1]; + + return SpecialPowers.wrap(document) + .getAnonymousElementByAttribute(videoControl, aName, aValue); + } + + function loadedmetadata() { + executeTestCases(testCases).then(SimpleTest.finish); + } + + function startMediaLoad() { + video.src = "seek_with_sound.ogg"; + video.addEventListener("loadedmetadata", loadedmetadata, false); + } + + function loadevent() { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad); + } + + window.addEventListener("load", loadevent, false); +</script> +</pre> +</body> +</html> diff --git a/toolkit/content/tests/widgets/tree_shared.js b/toolkit/content/tests/widgets/tree_shared.js new file mode 100644 index 0000000000..b157bdf564 --- /dev/null +++ b/toolkit/content/tests/widgets/tree_shared.js @@ -0,0 +1,1405 @@ +var columns_simpletree = +[ + { name: "name", label: "Name", key: true, properties: "one two" }, + { name: "address", label: "Address" } +]; + +var columns_hiertree = +[ + { name: "name", label: "Name", primary: true, key: true, properties: "one two" }, + { name: "address", label: "Address" }, + { name: "planet", label: "Planet" }, + { name: "gender", label: "Gender", cycler: true } +]; + +// XXXndeakin still to add some tests for: +// cycler columns, checkbox cells, progressmeter cells + +// this test function expects a tree to have 8 rows in it when it isn't +// expanded. The tree should only display four rows at a time. If editable, +// the cell at row 1 and column 0 must be editable, and the cell at row 2 and +// column 1 must not be editable. +function testtag_tree(treeid, treerowinfoid, seltype, columnstype, testid) +{ + // Stop keystrokes that aren't handled by the tree from leaking out and + // scrolling the main Mochitests window! + function preventDefault(event) { + event.preventDefault(); + } + document.addEventListener("keypress", preventDefault, false); + + var multiple = (seltype == "multiple"); + + var tree = document.getElementById(treeid); + var treerowinfo = document.getElementById(treerowinfoid); + var rowInfo; + if (testid =="tree view") + rowInfo = getCustomTreeViewCellInfo(); + else + rowInfo = convertDOMtoTreeRowInfo(treerowinfo, 0, { value: -1 }); + var columnInfo = (columnstype == "simple") ? columns_simpletree : columns_hiertree; + + is(tree.view.selection.currentColumn, null, testid + " initial currentColumn"); + is(tree.selType, seltype == "multiple" ? "" : seltype, testid + " seltype"); + + // note: the functions below should be in this order due to changes made in later tests + + // select the first column in cell selection mode so that the selection + // functions can be tested + if (seltype == "cell") + tree.view.selection.currentColumn = tree.columns[0]; + + testtag_tree_columns(tree, columnInfo, testid); + testtag_tree_TreeSelection(tree, testid, multiple); + testtag_tree_TreeSelection_UI(tree, testid, multiple); + if (seltype == "cell") + testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo); + + testtag_tree_TreeView(tree, testid, rowInfo); + + is(tree.editable, false, "tree should not be editable"); + // currently, the editable flag means that tree editing cannot be invoked + // by the user. However, editing can still be started with a script. + is(tree.editingRow, -1, testid + " initial editingRow"); + is(tree.editingColumn, null, testid + " initial editingColumn"); + + testtag_tree_UI_editing(tree, testid, rowInfo); + + is(tree.editable, false, "tree should not be editable after testtag_tree_UI_editing"); + // currently, the editable flag means that tree editing cannot be invoked + // by the user. However, editing can still be started with a script. + is(tree.editingRow, -1, testid + " initial editingRow (continued)"); + is(tree.editingColumn, null, testid + " initial editingColumn (continued)"); + + var ecolumn = tree.columns[0]; + ok(!tree.startEditing(1, ecolumn), "non-editable trees shouldn't start editing"); + is(tree.editingRow, -1, testid + " failed startEditing shouldn't set editingRow"); + is(tree.editingColumn, null, testid + " failed startEditing shouldn't set editingColumn"); + + tree.editable = true; + + ok(tree.startEditing(1, ecolumn), "startEditing should have returned true"); + is(tree.editingRow, 1, testid + " startEditing editingRow"); + is(tree.editingColumn, ecolumn, testid + " startEditing editingColumn"); + is(tree.getAttribute("editing"), "true", testid + " startEditing editing attribute"); + + tree.stopEditing(true); + is(tree.editingRow, -1, testid + " stopEditing editingRow"); + is(tree.editingColumn, null, testid + " stopEditing editingColumn"); + is(tree.hasAttribute("editing"), false, testid + " stopEditing editing attribute"); + + tree.startEditing(-1, ecolumn); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing -1 editingRow"); + tree.startEditing(15, ecolumn); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing 15 editingRow"); + tree.startEditing(1, null); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing null column editingRow"); + tree.startEditing(2, tree.columns[1]); + is(tree.editingRow == -1 && tree.editingColumn == null, true, testid + " startEditing non editable cell editingRow"); + + tree.startEditing(1, ecolumn); + var inputField = tree.inputField; + is(inputField instanceof Components.interfaces.nsIDOMXULTextBoxElement, true, testid + "inputField"); + inputField.value = "Changed Value"; + tree.stopEditing(true); + is(tree.view.getCellText(1, ecolumn), "Changed Value", testid + "edit cell accept"); + + // this cell can be edited, but stopEditing(false) means don't accept the change. + tree.startEditing(1, ecolumn); + inputField.value = "Second Value"; + tree.stopEditing(false); + is(tree.view.getCellText(1, ecolumn), "Changed Value", testid + "edit cell no accept"); + + tree.editable = false; + + // do the sorting tests last as it will cause the rows to rearrange + // skip them for the custom tree view + if (testid !="tree view") + testtag_tree_TreeView_rows_sort(tree, testid, rowInfo); + + testtag_tree_wheel(tree); + + document.removeEventListener("keypress", preventDefault, false); + + SimpleTest.finish(); +} + +function testtag_tree_columns(tree, expectedColumns, testid) +{ + testid += " "; + + var columns = tree.columns; + + is(columns instanceof TreeColumns, true, testid + "columns is a TreeColumns"); + is(columns.count, expectedColumns.length, testid + "TreeColumns count"); + is(columns.length, expectedColumns.length, testid + "TreeColumns length"); + + var treecols = tree.getElementsByTagName("treecols")[0]; + var treecol = treecols.getElementsByTagName("treecol"); + + var x = 0; + var primary = null, sorted = null, key = null; + for (var c = 0; c < expectedColumns.length; c++) { + var adjtestid = testid + " column " + c + " "; + var column = columns[c]; + var expectedColumn = expectedColumns[c]; + is(columns.getColumnAt(c), column, adjtestid + "getColumnAt"); + is(columns.getNamedColumn(expectedColumn.name), column, adjtestid + "getNamedColumn"); + is(columns.getColumnFor(treecol[c]), column, adjtestid + "getColumnFor"); + if (expectedColumn.primary) + primary = column; + if (expectedColumn.sorted) + sorted = column; + if (expectedColumn.key) + key = column; + + // XXXndeakin on Windows and Linux, some columns are one pixel to the + // left of where they should be. Could just be a rounding issue. + var adj = 1; + is(column.x + adj >= x, true, adjtestid + "position is after last column " + + column.x + "," + column.width + "," + x); + is(column.width > 0, true, adjtestid + "width is greater than 0"); + x = column.x + column.width; + + // now check the TreeColumn properties + is(column instanceof TreeColumn, true, adjtestid + "is a TreeColumn"); + is(column.element, treecol[c], adjtestid + "element is treecol"); + is(column.columns, columns, adjtestid + "columns is TreeColumns"); + is(column.id, expectedColumn.name, adjtestid + "name"); + is(column.index, c, adjtestid + "index"); + is(column.primary, primary == column, adjtestid + "column is primary"); + + is(column.cycler, "cycler" in expectedColumn && expectedColumn.cycler, + adjtestid + "column is cycler"); + is(column.selectable, true, adjtestid + "column is selectable"); + is(column.editable, "editable" in expectedColumn && expectedColumn.editable, + adjtestid + "column is editable"); + + is(column.type, "type" in expectedColumn ? expectedColumn.type : 1, adjtestid + "type"); + + is(column.getPrevious(), c > 0 ? columns[c - 1] : null, adjtestid + "getPrevious"); + is(column.getNext(), c < columns.length - 1 ? columns[c + 1] : null, adjtestid + "getNext"); + + // check the view's getColumnProperties method + var properties = tree.view.getColumnProperties(column); + var expectedProperties = expectedColumn.properties; + is(properties, expectedProperties ? expectedProperties : "", adjtestid + "getColumnProperties"); + } + + is(columns.getFirstColumn(), columns[0], testid + "getFirstColumn"); + is(columns.getLastColumn(), columns[columns.length - 1], testid + "getLastColumn"); + is(columns.getPrimaryColumn(), primary, testid + "getPrimaryColumn"); + is(columns.getSortedColumn(), sorted, testid + "getSortedColumn"); + is(columns.getKeyColumn(), key, testid + "getKeyColumn"); + + is(columns.getColumnAt(-1), null, testid + "getColumnAt under"); + is(columns.getColumnAt(columns.length), null, testid + "getColumnAt over"); + is(columns.getNamedColumn(""), null, testid + "getNamedColumn null"); + is(columns.getNamedColumn("unknown"), null, testid + "getNamedColumn unknown"); + is(columns.getColumnFor(null), null, testid + "getColumnFor null"); + is(columns.getColumnFor(tree), null, testid + "getColumnFor other"); +} + +function testtag_tree_TreeSelection(tree, testid, multiple) +{ + testid += " selection "; + + var selection = tree.view.selection; + is(selection instanceof Components.interfaces.nsITreeSelection, true, + testid + "selection is a TreeSelection"); + is(selection.single, !multiple, testid + "single"); + + testtag_tree_TreeSelection_State(tree, testid + "initial", -1, []); + is(selection.shiftSelectPivot, -1, testid + "initial shiftSelectPivot"); + + selection.currentIndex = 2; + testtag_tree_TreeSelection_State(tree, testid + "set currentIndex", 2, []); + tree.currentIndex = 3; + testtag_tree_TreeSelection_State(tree, testid + "set tree.currentIndex", 3, []); + + // test the select() method, which should deselect all rows and select + // a single row + selection.select(1); + testtag_tree_TreeSelection_State(tree, testid + "select 1", 1, [1]); + selection.select(3); + testtag_tree_TreeSelection_State(tree, testid + "select 2", 3, [3]); + selection.select(3); + testtag_tree_TreeSelection_State(tree, testid + "select same", 3, [3]); + + selection.currentIndex = 1; + testtag_tree_TreeSelection_State(tree, testid + "set currentIndex with single selection", 1, [3]); + + tree.currentIndex = 2; + testtag_tree_TreeSelection_State(tree, testid + "set tree.currentIndex with single selection", 2, [3]); + + // check the toggleSelect method. In single selection mode, it only toggles on when + // there isn't currently a selection. + selection.toggleSelect(2); + testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 1", 2, multiple ? [2, 3] : [3]); + selection.toggleSelect(2); + selection.toggleSelect(3); + testtag_tree_TreeSelection_State(tree, testid + "toggleSelect 2", 3, []); + + // the current index doesn't change after a selectAll, so it should still be set to 1 + // selectAll has no effect on single selection trees + selection.currentIndex = 1; + selection.selectAll(); + testtag_tree_TreeSelection_State(tree, testid + "selectAll 1", 1, multiple ? [0, 1, 2, 3, 4, 5, 6, 7] : []); + selection.toggleSelect(2); + testtag_tree_TreeSelection_State(tree, testid + "toggleSelect after selectAll", 2, + multiple ? [0, 1, 3, 4, 5, 6, 7] : [2]); + selection.clearSelection(); + testtag_tree_TreeSelection_State(tree, testid + "clearSelection", 2, []); + selection.toggleSelect(3); + selection.toggleSelect(1); + if (multiple) { + selection.selectAll(); + testtag_tree_TreeSelection_State(tree, testid + "selectAll 2", 1, [0, 1, 2, 3, 4, 5, 6, 7]); + } + selection.currentIndex = 2; + selection.clearSelection(); + testtag_tree_TreeSelection_State(tree, testid + "clearSelection after selectAll", 2, []); + + // XXXndeakin invertSelection isn't implemented + // selection.invertSelection(); + + is(selection.shiftSelectPivot, -1, testid + "shiftSelectPivot set to -1"); + + // rangedSelect and clearRange set the currentIndex to the endIndex. The + // shiftSelectPivot property will be set to startIndex. + selection.rangedSelect(1, 3, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect no augment", + multiple ? 3 : 2, multiple ? [1, 2, 3] : []); + is(selection.shiftSelectPivot, multiple ? 1 : -1, + testid + "shiftSelectPivot after rangedSelect no augment"); + if (multiple) { + selection.select(1); + selection.rangedSelect(0, 2, true); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 2, [0, 1, 2]); + is(selection.shiftSelectPivot, 0, testid + "shiftSelectPivot after rangedSelect augment"); + + selection.clearRange(1, 3); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment", 3, [0]); + + // check that rangedSelect can take a start value higher than end + selection.rangedSelect(3, 1, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect reverse", 1, [1, 2, 3]); + is(selection.shiftSelectPivot, 3, testid + "shiftSelectPivot after rangedSelect reverse"); + + // check that setting the current index doesn't change the selection + selection.currentIndex = 0; + testtag_tree_TreeSelection_State(tree, testid + "currentIndex with range selection", 0, [1, 2, 3]); + } + + // both values of rangedSelect may be the same + selection.rangedSelect(2, 2, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect one row", 2, [2]); + is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot after selecting one row"); + + if (multiple) { + selection.rangedSelect(2, 3, true); + + // a start index of -1 means from the last point + selection.rangedSelect(-1, 0, true); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect -1 existing selection", 0, [0, 1, 2, 3]); + is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot after -1 existing selection"); + + selection.currentIndex = 2; + selection.rangedSelect(-1, 0, false); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect -1 from currentIndex", 0, [0, 1, 2]); + is(selection.shiftSelectPivot, 2, testid + "shiftSelectPivot -1 from currentIndex"); + } + + // XXXndeakin need to test out of range values but these don't work properly +/* + selection.select(-1); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment -1", -1, []); + + selection.select(8); + testtag_tree_TreeSelection_State(tree, testid + "rangedSelect augment 8", 3, [0]); +*/ +} + +function testtag_tree_TreeSelection_UI(tree, testid, multiple) +{ + testid += " selection UI "; + + var selection = tree.view.selection; + selection.clearSelection(); + selection.currentIndex = 0; + tree.focus(); + + var keydownFired = 0; + var keypressFired = 0; + function keydownListener(event) + { + keydownFired++; + } + function keypressListener(event) { + keypressFired++; + } + + // check that cursor up and down keys navigate up and down + // select event fires after a delay so don't expect it. The reason it fires after a delay + // is so that cursor navigation allows quicking skimming over a set of items without + // actually firing events in-between, improving performance. The select event will only + // be fired on the row where the cursor stops. + window.addEventListener("keydown", keydownListener, false); + window.addEventListener("keypress", keypressListener, false); + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down"); + testtag_tree_TreeSelection_State(tree, testid + "key down", 1, [1], 0); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up"); + testtag_tree_TreeSelection_State(tree, testid + "key up", 0, [0], 0); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up at start"); + testtag_tree_TreeSelection_State(tree, testid + "key up at start", 0, [0], 0); + + // pressing down while the last row is selected should not fire a select event, + // as the selection won't have changed. Also the view is not scrolled in this case. + selection.select(7); + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down at end"); + testtag_tree_TreeSelection_State(tree, testid + "key down at end", 7, [7], 0); + + // pressing keys while at the edge of the visible rows should scroll the list + tree.treeBoxObject.scrollToRow(4); + selection.select(4); + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up with scroll"); + is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with scroll"); + + tree.treeBoxObject.scrollToRow(0); + selection.select(3); + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down with scroll"); + is(tree.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with scroll"); + + // accel key and cursor movement adjust currentIndex but should not change + // the selection. In single selection mode, the selection will not change, + // but instead will just scroll up or down a line + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + synthesizeKeyExpectEvent("VK_DOWN", { accelKey: true }, tree, "!select", "key down with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key down with accel", multiple ? 2 : 1, [1]); + if (!multiple) + is(tree.treeBoxObject.getFirstVisibleRow(), 1, testid + "key down with accel and scroll"); + + tree.treeBoxObject.scrollToRow(4); + selection.select(4); + synthesizeKeyExpectEvent("VK_UP", { accelKey: true }, tree, "!select", "key up with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key up with accel", multiple ? 3 : 4, [4]); + if (!multiple) + is(tree.treeBoxObject.getFirstVisibleRow(), 3, testid + "key up with accel and scroll"); + + // do this three times, one for each state of pageUpOrDownMovesSelection, + // and then once with the accel key pressed + for (let t = 0; t < 3; t++) { + let testidmod = ""; + if (t == 2) + testidmod = " with accel" + else if (t == 1) + testidmod = " rev"; + var keymod = (t == 2) ? { accelKey: true } : { }; + + var moveselection = tree.pageUpOrDownMovesSelection; + if (t == 2) + moveselection = !moveselection; + + tree.treeBoxObject.scrollToRow(4); + selection.currentIndex = 6; + selection.select(6); + var expected = moveselection ? 4 : 6; + synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up"); + testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, + expected, [expected], moveselection ? 4 : 0); + + expected = moveselection ? 0 : 6; + synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up again"); + testtag_tree_TreeSelection_State(tree, testid + "key page up again" + testidmod, + expected, [expected], 0); + + expected = moveselection ? 0 : 6; + synthesizeKeyExpectEvent("VK_PAGE_UP", keymod, tree, "!select", "key page up at start"); + testtag_tree_TreeSelection_State(tree, testid + "key page up at start" + testidmod, + expected, [expected], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.currentIndex = 1; + selection.select(1); + expected = moveselection ? 3 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down"); + testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, + expected, [expected], moveselection ? 0 : 4); + + expected = moveselection ? 7 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down again"); + testtag_tree_TreeSelection_State(tree, testid + "key page down again" + testidmod, + expected, [expected], 4); + + expected = moveselection ? 7 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", keymod, tree, "!select", "key page down at start"); + testtag_tree_TreeSelection_State(tree, testid + "key page down at start" + testidmod, + expected, [expected], 4); + + if (t < 2) + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + tree.treeBoxObject.scrollToRow(4); + selection.select(6); + synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); + testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); + testtag_tree_TreeSelection_State(tree, testid + "key end", 7, [7], 4); + + // in single selection mode, the selection doesn't change in this case + tree.treeBoxObject.scrollToRow(4); + selection.select(6); + synthesizeKeyExpectEvent("VK_HOME", { accelKey: true }, tree, "!select", "key home with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key home with accel", multiple ? 0 : 6, [6], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + synthesizeKeyExpectEvent("VK_END", { accelKey: true }, tree, "!select", "key end with accel"); + testtag_tree_TreeSelection_State(tree, testid + "key end with accel", multiple ? 7 : 1, [1], 4); + + // next, test cursor navigation with selection. Here the select event will be fired + selection.select(1); + var eventExpected = multiple ? "select" : "!select"; + synthesizeKeyExpectEvent("VK_DOWN", { shiftKey: true }, tree, eventExpected, "key shift down to select"); + testtag_tree_TreeSelection_State(tree, testid + "key shift down to select", + multiple ? 2 : 1, multiple ? [1, 2] : [1]); + is(selection.shiftSelectPivot, multiple ? 1 : -1, + testid + "key shift down to select shiftSelectPivot"); + synthesizeKeyExpectEvent("VK_UP", { shiftKey: true }, tree, eventExpected, "key shift up to unselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift up to unselect", 1, [1]); + is(selection.shiftSelectPivot, multiple ? 1 : -1, + testid + "key shift up to unselect shiftSelectPivot"); + if (multiple) { + synthesizeKeyExpectEvent("VK_UP", { shiftKey: true }, tree, "select", "key shift up to select"); + testtag_tree_TreeSelection_State(tree, testid + "key shift up to select", 0, [0, 1]); + is(selection.shiftSelectPivot, 1, testid + "key shift up to select shiftSelectPivot"); + synthesizeKeyExpectEvent("VK_DOWN", { shiftKey: true }, tree, "select", "key shift down to unselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift down to unselect", 1, [1]); + is(selection.shiftSelectPivot, 1, testid + "key shift down to unselect shiftSelectPivot"); + } + + // do this twice, one for each state of pageUpOrDownMovesSelection, however + // when selecting with the shift key, pageUpOrDownMovesSelection is ignored + // and the selection always changes + var lastidx = tree.view.rowCount - 1; + for (let t = 0; t < 2; t++) { + let testidmod = (t == 0) ? "" : " rev"; + + // If the top or bottom visible row is the current row, pressing shift and + // page down / page up selects one page up or one page down. Otherwise, the + // selection is made to the top or bottom of the visible area. + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.currentIndex = 6; + selection.select(6); + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, eventExpected, "key shift page up"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up" + testidmod, + multiple ? 4 : 6, multiple ? [4, 5, 6] : [6]); + if (multiple) { + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "select", "key shift page up again"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up again" + testidmod, + 0, [0, 1, 2, 3, 4, 5, 6]); + // no change in the selection, so no select event should be fired + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "!select", "key shift page up at start"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up at start" + testidmod, + 0, [0, 1, 2, 3, 4, 5, 6]); + // deselect by paging down again + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "select", "key shift page down deselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down deselect" + testidmod, + 3, [3, 4, 5, 6]); + } + + tree.treeBoxObject.scrollToRow(1); + selection.currentIndex = 2; + selection.select(2); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, eventExpected, "key shift page down"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down" + testidmod, + multiple ? 4 : 2, multiple ? [2, 3, 4] : [2]); + if (multiple) { + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "select", "key shift page down again"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down again" + testidmod, + 7, [2, 3, 4, 5, 6, 7]); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "!select", "key shift page down at start"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down at start" + testidmod, + 7, [2, 3, 4, 5, 6, 7]); + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "select", "key shift page up deselect"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up deselect" + testidmod, + 4, [2, 3, 4]); + } + + // test when page down / page up is pressed when the view is scrolled such + // that the selection is not visible + if (multiple) { + tree.treeBoxObject.scrollToRow(3); + selection.currentIndex = 1; + selection.select(1); + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, eventExpected, + "key shift page down with view scrolled down"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down with view scrolled down" + testidmod, + 6, [1, 2, 3, 4, 5, 6], 3); + + tree.treeBoxObject.scrollToRow(2); + selection.currentIndex = 6; + selection.select(6); + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, eventExpected, + "key shift page up with view scrolled up"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up with view scrolled up" + testidmod, + 2, [2, 3, 4, 5, 6], 2); + + tree.treeBoxObject.scrollToRow(2); + selection.currentIndex = 0; + selection.select(0); + // don't expect the select event, as the selection won't have changed + synthesizeKeyExpectEvent("VK_PAGE_UP", { shiftKey: true }, tree, "!select", + "key shift page up at start with view scrolled down"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page up at start with view scrolled down" + testidmod, + 0, [0], 0); + + tree.treeBoxObject.scrollToRow(0); + selection.currentIndex = 7; + selection.select(7); + // don't expect the select event, as the selection won't have changed + synthesizeKeyExpectEvent("VK_PAGE_DOWN", { shiftKey: true }, tree, "!select", + "key shift page down at end with view scrolled up"); + testtag_tree_TreeSelection_State(tree, testid + "key shift page down at end with view scrolled up" + testidmod, + 7, [7], 4); + } + + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + tree.treeBoxObject.scrollToRow(4); + selection.select(5); + synthesizeKeyExpectEvent("VK_HOME", { shiftKey: true }, tree, eventExpected, "key shift home"); + testtag_tree_TreeSelection_State(tree, testid + "key shift home", + multiple ? 0 : 5, multiple ? [0, 1, 2, 3, 4, 5] : [5], multiple ? 0 : 4); + + tree.treeBoxObject.scrollToRow(0); + selection.select(3); + synthesizeKeyExpectEvent("VK_END", { shiftKey: true }, tree, eventExpected, "key shift end"); + testtag_tree_TreeSelection_State(tree, testid + "key shift end", + multiple ? 7 : 3, multiple ? [3, 4, 5, 6, 7] : [3], multiple ? 4 : 0); + + // pressing space selects a row, pressing accel + space unselects a row + selection.select(2); + selection.currentIndex = 4; + synthesizeKeyExpectEvent(" ", {}, tree, "select", "key space on"); + // in single selection mode, space shouldn't do anything + testtag_tree_TreeSelection_State(tree, testid + "key space on", 4, multiple ? [2, 4] : [2]); + + if (multiple) { + synthesizeKeyExpectEvent(" ", { accelKey: true }, tree, "select", "key space off"); + testtag_tree_TreeSelection_State(tree, testid + "key space off", 4, [2]); + } + + // check that clicking on a row selects it + tree.treeBoxObject.scrollToRow(0); + selection.select(2); + selection.currentIndex = 2; + if (0) { // XXXndeakin disable these tests for now + mouseOnCell(tree, 1, tree.columns[1], "mouse on row"); + testtag_tree_TreeSelection_State(tree, testid + "mouse on row", 1, [1], 0, + tree.selType == "cell" ? tree.columns[1] : null); + } + + // restore the scroll position to the start of the page + sendKey("HOME"); + + window.removeEventListener("keydown", keydownListener, false); + window.removeEventListener("keypress", keypressListener, false); + is(keydownFired, multiple ? 63 : 40, "keydown event wasn't fired properly"); + is(keypressFired, multiple ? 2 : 1, "keypress event wasn't fired properly"); +} + +function testtag_tree_UI_editing(tree, testid, rowInfo) +{ + testid += " editing UI "; + + // check editing UI + var ecolumn = tree.columns[0]; + var rowIndex = 2; + var inputField = tree.inputField; + + // temporary make the tree editable to test mouse double click + var wasEditable = tree.editable; + if (!wasEditable) + tree.editable = true; + + // if this is a container save its current open status + var row = rowInfo.rows[rowIndex]; + var wasOpen = null; + if (tree.view.isContainer(row)) + wasOpen = tree.view.isContainerOpen(row); + + // Test whether a keystroke can enter text entry, and another can exit. + if (tree.selType == "cell") + { + tree.stopEditing(false); + ok(!tree.editingColumn, "Should not be editing tree cell now"); + tree.view.selection.currentColumn = ecolumn; + tree.currentIndex = rowIndex; + + const isMac = (navigator.platform.indexOf("Mac") >= 0); + const StartEditingKey = isMac ? "RETURN" : "F2"; + sendKey(StartEditingKey); + is(tree.editingColumn, ecolumn, "Should be editing tree cell now"); + sendKey("ESCAPE"); + ok(!tree.editingColumn, "Should not be editing tree cell now"); + is(tree.currentIndex, rowIndex, "Current index should not have changed"); + is(tree.view.selection.currentColumn, ecolumn, "Current column should not have changed"); + } + + mouseDblClickOnCell(tree, rowIndex, ecolumn, testid + "edit on double click"); + is(tree.editingColumn, ecolumn, testid + "editing column"); + is(tree.editingRow, rowIndex, testid + "editing row"); + + // ensure that we don't expand an expandable container on edit + if (wasOpen != null) + is(tree.view.isContainerOpen(row), wasOpen, testid + "opened container node on edit"); + + // ensure to restore editable attribute + if (!wasEditable) + tree.editable = false; + + var ci = tree.currentIndex; + + // cursor navigation should not change the selection while editing + var testKey = function(key) { + synthesizeKeyExpectEvent(key, {}, tree, "!select", "key " + key + " with editing"); + is(tree.editingRow == rowIndex && tree.editingColumn == ecolumn && tree.currentIndex == ci, + true, testid + "key " + key + " while editing"); + } + + testKey("VK_DOWN"); + testKey("VK_UP"); + testKey("VK_PAGE_DOWN"); + testKey("VK_PAGE_UP"); + testKey("VK_HOME"); + testKey("VK_END"); + + // XXXndeakin figure out how to send characters to the textbox + // inputField.inputField.focus() + // synthesizeKeyExpectEvent(inputField.inputField, "b", null, ""); + // tree.stopEditing(true); + // is(tree.view.getCellText(0, ecolumn), "b", testid + "edit cell"); + + // Restore initial state. + tree.stopEditing(false); +} + +function testtag_tree_TreeSelection_UI_cell(tree, testid, rowInfo) +{ + testid += " selection UI cell "; + + var columns = tree.columns; + var firstcolumn = columns[0]; + var secondcolumn = columns[1]; + var lastcolumn = columns[columns.length - 1]; + var secondlastcolumn = columns[columns.length - 2]; + var selection = tree.view.selection; + + selection.clearSelection(); + selection.currentIndex = -1; + selection.currentColumn = firstcolumn; + is(selection.currentColumn, firstcolumn, testid + " first currentColumn"); + + // no selection yet so nothing should happen when the left and right cursor keys are pressed + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right no selection"); + testtag_tree_TreeSelection_State(tree, testid + "key right no selection", -1, [], null, firstcolumn); + + selection.currentColumn = secondcolumn; + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left no selection"); + testtag_tree_TreeSelection_State(tree, testid + "key left no selection", -1, [], null, secondcolumn); + + selection.select(2); + selection.currentIndex = 2; + if (0) { // XXXndeakin disable these tests for now + mouseOnCell(tree, 1, secondlastcolumn, "mouse on cell"); + testtag_tree_TreeSelection_State(tree, testid + "mouse on cell", 1, [1], null, secondlastcolumn); + } + + tree.focus(); + + // selection is set, so it should move when the left and right cursor keys are pressed + tree.treeBoxObject.scrollToRow(0); + selection.select(1); + selection.currentIndex = 1; + selection.currentColumn = secondcolumn; + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in second column"); + testtag_tree_TreeSelection_State(tree, testid + "key left in second column", 1, [1], 0, firstcolumn); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left in first column"); + testtag_tree_TreeSelection_State(tree, testid + "key left in first column", 1, [1], 0, firstcolumn); + + selection.currentColumn = secondlastcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in second last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right in second last column", 1, [1], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right in last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right in last column", 1, [1], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in second row"); + testtag_tree_TreeSelection_State(tree, testid + "key up in second row", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up in first row"); + testtag_tree_TreeSelection_State(tree, testid + "key up in first row", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in first row"); + testtag_tree_TreeSelection_State(tree, testid + "key down in first row", 1, [1], 0, lastcolumn); + + var lastidx = tree.view.rowCount - 1; + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.select(lastidx); + selection.currentIndex = lastidx; + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down in last row"); + testtag_tree_TreeSelection_State(tree, testid + "key down in last row", lastidx, [lastidx], lastidx - 3, lastcolumn); + + synthesizeKeyExpectEvent("VK_HOME", {}, tree, "!select", "key home"); + testtag_tree_TreeSelection_State(tree, testid + "key home", 0, [0], 0, lastcolumn); + + synthesizeKeyExpectEvent("VK_END", {}, tree, "!select", "key end"); + testtag_tree_TreeSelection_State(tree, testid + "key end", lastidx, [lastidx], lastidx - 3, lastcolumn); + + for (var t = 0; t < 2; t++) { + var testidmod = (t == 0) ? "" : " rev"; + + // scroll to the end, subtract 3 because we want lastidx to appear + // at the end of view + tree.treeBoxObject.scrollToRow(lastidx - 3); + selection.select(lastidx); + selection.currentIndex = lastidx; + var expectedrow = tree.pageUpOrDownMovesSelection ? lastidx - 3 : lastidx; + synthesizeKeyExpectEvent("VK_PAGE_UP", {}, tree, "!select", "key page up"); + testtag_tree_TreeSelection_State(tree, testid + "key page up" + testidmod, + expectedrow, [expectedrow], + tree.pageUpOrDownMovesSelection ? lastidx - 3 : 0, lastcolumn); + + tree.treeBoxObject.scrollToRow(1); + selection.select(1); + selection.currentIndex = 1; + expectedrow = tree.pageUpOrDownMovesSelection ? 4 : 1; + synthesizeKeyExpectEvent("VK_PAGE_DOWN", {}, tree, "!select", "key page down"); + testtag_tree_TreeSelection_State(tree, testid + "key page down" + testidmod, + expectedrow, [expectedrow], + tree.pageUpOrDownMovesSelection ? 1 : lastidx - 3, lastcolumn); + + tree.pageUpOrDownMovesSelection = !tree.pageUpOrDownMovesSelection; + } + + // now check navigation when there is unselctable column + secondcolumn.element.setAttribute("selectable", "false"); + secondcolumn.invalidate(); + is(secondcolumn.selectable, false, testid + "set selectable attribute"); + + if (columns.length >= 3) { + selection.select(3); + selection.currentIndex = 3; + // check whether unselectable columns are skipped over + selection.currentColumn = firstcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable column"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable column", + 3, [3], null, secondcolumn.getNext()); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable column"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable column", + 3, [3], null, firstcolumn); + } + + secondcolumn.element.removeAttribute("selectable"); + secondcolumn.invalidate(); + is(secondcolumn.selectable, true, testid + "clear selectable attribute"); + + // check to ensure that navigation isn't allowed if the first column is not selectable + selection.currentColumn = secondcolumn; + firstcolumn.element.setAttribute("selectable", "false"); + firstcolumn.invalidate(); + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable first column"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable first column", + 3, [3], null, secondcolumn); + firstcolumn.element.removeAttribute("selectable"); + firstcolumn.invalidate(); + + // check to ensure that navigation isn't allowed if the last column is not selectable + selection.currentColumn = secondlastcolumn; + lastcolumn.element.setAttribute("selectable", "false"); + lastcolumn.invalidate(); + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable last column"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable last column", + 3, [3], null, secondlastcolumn); + lastcolumn.element.removeAttribute("selectable"); + lastcolumn.invalidate(); + + // now check for cells with selectable false + if (!rowInfo.rows[4].cells[1].selectable && columns.length >= 3) { + // check whether unselectable cells are skipped over + selection.select(4); + selection.currentIndex = 4; + + selection.currentColumn = firstcolumn; + synthesizeKeyExpectEvent("VK_RIGHT", {}, tree, "!select", "key right unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key right unselectable cell", + 4, [4], null, secondcolumn.getNext()); + + synthesizeKeyExpectEvent("VK_LEFT", {}, tree, "!select", "key left unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key left unselectable cell", + 4, [4], null, firstcolumn); + + tree.treeBoxObject.scrollToRow(1); + selection.select(3); + selection.currentIndex = 3; + selection.currentColumn = secondcolumn; + + synthesizeKeyExpectEvent("VK_DOWN", {}, tree, "!select", "key down unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key down unselectable cell", + 5, [5], 2, secondcolumn); + + tree.treeBoxObject.scrollToRow(4); + synthesizeKeyExpectEvent("VK_UP", {}, tree, "!select", "key up unselectable cell"); + testtag_tree_TreeSelection_State(tree, testid + "key up unselectable cell", + 3, [3], 3, secondcolumn); + } + + // restore the scroll position to the start of the page + sendKey("HOME"); +} + +function testtag_tree_TreeView(tree, testid, rowInfo) +{ + testid += " view "; + + var columns = tree.columns; + var view = tree.view; + + is(view instanceof Components.interfaces.nsITreeView, true, testid + "view is a TreeView"); + is(view.rowCount, rowInfo.rows.length, testid + "rowCount"); + + testtag_tree_TreeView_rows(tree, testid, rowInfo, 0); + + // note that this will only work for content trees currently + view.setCellText(0, columns[1], "Changed Value"); + is(view.getCellText(0, columns[1]), "Changed Value", "setCellText"); + + view.setCellValue(1, columns[0], "Another Changed Value"); + is(view.getCellValue(1, columns[0]), "Another Changed Value", "setCellText"); +} + +function testtag_tree_TreeView_rows(tree, testid, rowInfo, startRow) +{ + var r; + var columns = tree.columns; + var view = tree.view; + var length = rowInfo.rows.length; + + // methods to test along with the functions which determine the expected value + var checkRowMethods = + { + isContainer: function(row) { return row.container }, + isContainerOpen: function(row) { return false }, + isContainerEmpty: function(row) { return (row.children != null && row.children.rows.length == 0) }, + isSeparator: function(row) { return row.separator }, + getRowProperties: function(row) { return row.properties }, + getLevel: function(row) { return row.level }, + getParentIndex: function(row) { return row.parent }, + hasNextSibling: function(row) { return r < startRow + length - 1; } + }; + + var checkCellMethods = + { + getCellText: function(row, cell) { return cell.label }, + getCellValue: function(row, cell) { return cell.value }, + getCellProperties: function(row, cell) { return cell.properties }, + isEditable: function(row, cell) { return cell.editable }, + isSelectable: function(row, cell) { return cell.selectable }, + getImageSrc: function(row, cell) { return cell.image }, + getProgressMode: function(row, cell) { return cell.mode } + }; + + var failedMethods = { }; + var checkMethod, actual, expected; + var containerInfo = null; + var toggleOpenStateOK = true; + + for (r = startRow; r < length; r++) { + var row = rowInfo.rows[r]; + for (var c = 0; c < row.cells.length; c++) { + var cell = row.cells[c]; + + for (checkMethod in checkCellMethods) { + expected = checkCellMethods[checkMethod](row, cell); + actual = view[checkMethod](r, columns[c]); + if (actual !== expected) { + failedMethods[checkMethod] = true; + is(actual, expected, testid + "row " + r + " column " + c + " " + checkMethod + " is incorrect"); + } + } + } + + // compare row properties + for (checkMethod in checkRowMethods) { + expected = checkRowMethods[checkMethod](row, r); + if (checkMethod == "hasNextSibling") { + actual = view[checkMethod](r, r); + } + else { + actual = view[checkMethod](r); + } + if (actual !== expected) { + failedMethods[checkMethod] = true; + is(actual, expected, testid + "row " + r + " " + checkMethod + " is incorrect"); + } + } +/* + // open and recurse into containers + if (row.container) { + view.toggleOpenState(r); + if (!view.isContainerOpen(r)) { + toggleOpenStateOK = false; + is(view.isContainerOpen(r), true, testid + "row " + r + " toggleOpenState open"); + } + testtag_tree_TreeView_rows(tree, testid + "container " + r + " ", row.children, r + 1); + view.toggleOpenState(r); + if (view.isContainerOpen(r)) { + toggleOpenStateOK = false; + is(view.isContainerOpen(r), false, testid + "row " + r + " toggleOpenState close"); + } + } +*/ + } + + for (var failedMethod in failedMethods) { + if (failedMethod in checkRowMethods) + delete checkRowMethods[failedMethod]; + if (failedMethod in checkCellMethods) + delete checkCellMethods[failedMethod]; + } + + for (checkMethod in checkRowMethods) + is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod); + for (checkMethod in checkCellMethods) + is(checkMethod + " ok", checkMethod + " ok", testid + checkMethod); + if (toggleOpenStateOK) + is("toggleOpenState ok", "toggleOpenState ok", testid + "toggleOpenState"); +} + +function testtag_tree_TreeView_rows_sort(tree, testid, rowInfo) +{ + // check if cycleHeader sorts the columns + var columnIndex = 0; + var view = tree.view; + var column = tree.columns[columnIndex]; + var columnElement = column.element; + var sortkey = columnElement.getAttribute("sort"); + if (sortkey) { + view.cycleHeader(column); + is(tree.getAttribute("sort"), sortkey, "cycleHeader sort"); + is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending"); + is(columnElement.getAttribute("sortDirection"), "ascending", "cycleHeader column sortDirection"); + is(columnElement.getAttribute("sortActive"), "true", "cycleHeader column sortActive"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection descending"); + is(columnElement.getAttribute("sortDirection"), "descending", "cycleHeader column sortDirection descending"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "", "cycleHeader sortDirection natural"); + is(columnElement.getAttribute("sortDirection"), "", "cycleHeader column sortDirection natural"); + // XXXndeakin content view isSorted needs to be tested + } + + // Check that clicking on column header sorts the column. + var columns = getSortedColumnArray(tree); + is(columnElement.getAttribute("sortDirection"), "", + "cycleHeader column sortDirection"); + + // Click once on column header and check sorting has cycled once. + mouseClickOnColumnHeader(columns, columnIndex, 0, 1); + is(columnElement.getAttribute("sortDirection"), "ascending", + "single click cycleHeader column sortDirection ascending"); + + // Now simulate a double click. + mouseClickOnColumnHeader(columns, columnIndex, 0, 2); + if (navigator.platform.indexOf("Win") == 0) { + // Windows cycles only once on double click. + is(columnElement.getAttribute("sortDirection"), "descending", + "double click cycleHeader column sortDirection descending"); + // 1 single clicks should restore natural sorting. + mouseClickOnColumnHeader(columns, columnIndex, 0, 1); + } + + // Check we have gone back to natural sorting. + is(columnElement.getAttribute("sortDirection"), "", + "cycleHeader column sortDirection"); + + columnElement.setAttribute("sorthints", "twostate"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection ascending twostate"); + view.cycleHeader(column); + is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate again"); + columnElement.removeAttribute("sorthints"); + view.cycleHeader(column); + view.cycleHeader(column); + + is(columnElement.getAttribute("sortDirection"), "", + "cycleHeader column sortDirection reset"); +} + +// checks if the current and selected rows are correct +// current is the index of the current row +// selected is an array of the indicies of the selected rows +// column is the selected column +// viewidx is the row that should be visible at the top of the tree +function testtag_tree_TreeSelection_State(tree, testid, current, selected, viewidx, column) +{ + var selection = tree.view.selection; + + if (!column) + column = (tree.selType == "cell") ? tree.columns[0] : null; + + is(selection.count, selected.length, testid + " count"); + is(tree.currentIndex, current, testid + " currentIndex"); + is(selection.currentIndex, current, testid + " TreeSelection currentIndex"); + is(selection.currentColumn, column, testid + " currentColumn"); + if (viewidx !== null && viewidx !== undefined) + is(tree.treeBoxObject.getFirstVisibleRow(), viewidx, testid + " first visible row"); + + var actualSelected = []; + var count = tree.view.rowCount; + for (var s = 0; s < count; s++) { + if (selection.isSelected(s)) + actualSelected.push(s); + } + + is(compareArrays(selected, actualSelected), true, testid + " selection [" + selected + "]"); + + actualSelected = []; + var rangecount = selection.getRangeCount(); + for (var r = 0; r < rangecount; r++) { + var start = {}, end = {}; + selection.getRangeAt(r, start, end); + for (var rs = start.value; rs <= end.value; rs++) + actualSelected.push(rs); + } + + is(compareArrays(selected, actualSelected), true, testid + " range selection [" + selected + "]"); +} + +function testtag_tree_column_reorder() +{ + // Make sure the tree is scrolled into the view, otherwise the test will + // fail + var testframe = window.parent.document.getElementById("testframe"); + if (testframe) { + testframe.scrollIntoView(); + } + + var tree = document.getElementById("tree-column-reorder"); + var numColumns = tree.columns.count; + + var reference = []; + for (let i = 0; i < numColumns; i++) { + reference.push("col_" + i); + } + + // Drag the first column to each position + for (let i = 0; i < numColumns - 1; i++) { + synthesizeColumnDrag(tree, i, i + 1, true); + arrayMove(reference, i, i + 1, true); + checkColumns(tree, reference, "drag first column right"); + } + + // And back + for (let i = numColumns - 1; i >= 1; i--) { + synthesizeColumnDrag(tree, i, i - 1, false); + arrayMove(reference, i, i - 1, false); + checkColumns(tree, reference, "drag last column left"); + } + + // Drag each column one column left + for (let i = 1; i < numColumns; i++) { + synthesizeColumnDrag(tree, i, i - 1, false); + arrayMove(reference, i, i - 1, false); + checkColumns(tree, reference, "drag each column left"); + } + + // And back + for (let i = numColumns - 2; i >= 0; i--) { + synthesizeColumnDrag(tree, i, i + 1, true); + arrayMove(reference, i, i + 1, true); + checkColumns(tree, reference, "drag each column right"); + } + + // Drag each column 5 to the right + for (let i = 0; i < numColumns - 5; i++) { + synthesizeColumnDrag(tree, i, i + 5, true); + arrayMove(reference, i, i + 5, true); + checkColumns(tree, reference, "drag each column 5 to the right"); + } + + // And to the left + for (let i = numColumns - 6; i >= 5; i--) { + synthesizeColumnDrag(tree, i, i - 5, false); + arrayMove(reference, i, i - 5, false); + checkColumns(tree, reference, "drag each column 5 to the left"); + } + + // Test that moving a column after itself does not move anything + synthesizeColumnDrag(tree, 0, 0, true); + checkColumns(tree, reference, "drag to itself"); + is(document.treecolDragging, null, "drag to itself completed"); + + // XXX roc should this be here??? + SimpleTest.finish(); +} + +function testtag_tree_wheel(aTree) +{ + const deltaModes = [ + WheelEvent.DOM_DELTA_PIXEL, // 0 + WheelEvent.DOM_DELTA_LINE, // 1 + WheelEvent.DOM_DELTA_PAGE // 2 + ]; + function helper(aStart, aDelta, aIntDelta, aDeltaMode) + { + aTree.treeBoxObject.scrollToRow(aStart); + var expected; + if (!aIntDelta) { + expected = aStart; + } + else if (aDeltaMode != WheelEvent.DOM_DELTA_PAGE) { + expected = aStart + aIntDelta; + } + else if (aIntDelta > 0) { + expected = aStart + aTree.treeBoxObject.getPageLength(); + } + else { + expected = aStart - aTree.treeBoxObject.getPageLength(); + } + + if (expected < 0) { + expected = 0; + } + if (expected > aTree.view.rowCount - aTree.treeBoxObject.getPageLength()) { + expected = aTree.view.rowCount - aTree.treeBoxObject.getPageLength(); + } + synthesizeWheel(aTree.body, 1, 1, + { deltaMode: aDeltaMode, deltaY: aDelta, + lineOrPageDeltaY: aIntDelta }); + is(aTree.treeBoxObject.getFirstVisibleRow(), expected, + "testtag_tree_wheel: vertical, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + + aTree.treeBoxObject.scrollToRow(aStart); + // Check that horizontal scrolling has no effect + synthesizeWheel(aTree.body, 1, 1, + { deltaMode: aDeltaMode, deltaX: aDelta, + lineOrPageDeltaX: aIntDelta }); + is(aTree.treeBoxObject.getFirstVisibleRow(), aStart, + "testtag_tree_wheel: horizontal, starting " + aStart + + " delta " + aDelta + " lineOrPageDelta " + aIntDelta + + " aDeltaMode " + aDeltaMode); + } + + var defaultPrevented = 0; + + function wheelListener(event) { + defaultPrevented++; + } + window.addEventListener("wheel", wheelListener, false); + + deltaModes.forEach(function(aDeltaMode) { + var delta = (aDeltaMode == WheelEvent.DOM_DELTA_PIXEL) ? 5.0 : 0.3; + helper(2, -delta, 0, aDeltaMode); + helper(2, -delta, -1, aDeltaMode); + helper(2, delta, 0, aDeltaMode); + helper(2, delta, 1, aDeltaMode); + helper(2, -2 * delta, 0, aDeltaMode); + helper(2, -2 * delta, -1, aDeltaMode); + helper(2, 2 * delta, 0, aDeltaMode); + helper(2, 2 * delta, 1, aDeltaMode); + }); + + window.removeEventListener("wheel", wheelListener, false); + is(defaultPrevented, 48, "wheel event default prevented"); +} + +function synthesizeColumnDrag(aTree, aMouseDownColumnNumber, aMouseUpColumnNumber, aAfter) +{ + var columns = getSortedColumnArray(aTree); + + var down = columns[aMouseDownColumnNumber].element; + var up = columns[aMouseUpColumnNumber].element; + + // Target the initial mousedown in the middle of the column header so we + // avoid the extra hit test space given to the splitter + var columnWidth = down.boxObject.width; + var splitterHitWidth = columnWidth / 2; + synthesizeMouse(down, splitterHitWidth, 3, { type: "mousedown"}); + + var offsetX = 0; + if (aAfter) { + offsetX = columnWidth; + } + + if (aMouseUpColumnNumber > aMouseDownColumnNumber) { + for (let i = aMouseDownColumnNumber; i <= aMouseUpColumnNumber; i++) { + let move = columns[i].element; + synthesizeMouse(move, offsetX, 3, { type: "mousemove"}); + } + } + else { + for (let i = aMouseDownColumnNumber; i >= aMouseUpColumnNumber; i--) { + let move = columns[i].element; + synthesizeMouse(move, offsetX, 3, { type: "mousemove"}); + } + } + + synthesizeMouse(up, offsetX, 3, { type: "mouseup"}); +} + +function arrayMove(aArray, aFrom, aTo, aAfter) +{ + var o = aArray.splice(aFrom, 1)[0]; + if (aTo > aFrom) { + aTo--; + } + + if (aAfter) { + aTo++; + } + + aArray.splice(aTo, 0, o); +} + +function getSortedColumnArray(aTree) +{ + var columns = aTree.columns; + var array = []; + for (let i = 0; i < columns.length; i++) { + array.push(columns.getColumnAt(i)); + } + + array.sort(function(a, b) { + var o1 = parseInt(a.element.getAttribute("ordinal")); + var o2 = parseInt(b.element.getAttribute("ordinal")); + return o1 - o2; + }); + return array; +} + +function checkColumns(aTree, aReference, aMessage) +{ + var columns = getSortedColumnArray(aTree); + var ids = []; + columns.forEach(function(e) { + ids.push(e.element.id); + }); + is(compareArrays(ids, aReference), true, aMessage); +} + +function mouseOnCell(tree, row, column, testname) +{ + var rect = tree.boxObject.getCoordsForCellItem(row, column, "text"); + + synthesizeMouseExpectEvent(tree.body, rect.x, rect.y, {}, tree, "select", testname); +} + +function mouseClickOnColumnHeader(aColumns, aColumnIndex, aButton, aClickCount) +{ + var columnHeader = aColumns[aColumnIndex].element; + var columnHeaderRect = columnHeader.getBoundingClientRect(); + var columnWidth = columnHeaderRect.right - columnHeaderRect.left; + // For multiple click we send separate click events, with increasing + // clickCount. This simulates the common behavior of multiple clicks. + for (let i = 1; i <= aClickCount; i++) { + // Target the middle of the column header. + synthesizeMouse(columnHeader, columnWidth / 2, 3, + { button: aButton, + clickCount: i }, null); + } +} + +function mouseDblClickOnCell(tree, row, column, testname) +{ + // select the row we will edit + var selection = tree.view.selection; + selection.select(row); + tree.treeBoxObject.ensureRowIsVisible(row); + + // get cell coordinates + var rect = tree.treeBoxObject.getCoordsForCellItem(row, column, "text"); + + synthesizeMouse(tree.body, rect.x, rect.y, { clickCount: 2 }, null); +} + +function compareArrays(arr1, arr2) +{ + if (arr1.length != arr2.length) + return false; + + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) + return false; + } + + return true; +} + +function convertProperties(arr) +{ + var results = []; + var count = arr.Count(); + for (let i = 0; i < count; i++) + results.push(arr.GetElementAt(i).QueryInterface(Components.interfaces.nsIAtom).toString()); + + results.sort(); + return results.join(" "); +} + +function convertDOMtoTreeRowInfo(treechildren, level, rowidx) +{ + var obj = { rows: [] }; + + var parentidx = rowidx.value; + + treechildren = treechildren.childNodes; + for (var r = 0; r < treechildren.length; r++) { + rowidx.value++; + + var treeitem = treechildren[r]; + if (treeitem.hasChildNodes()) { + var treerow = treeitem.firstChild; + var cellInfo = []; + for (var c = 0; c < treerow.childNodes.length; c++) { + var cell = treerow.childNodes[c]; + cellInfo.push({ label: "" + cell.getAttribute("label"), + value: cell.getAttribute("value"), + properties: cell.getAttribute("properties"), + editable: cell.getAttribute("editable") != "false", + selectable: cell.getAttribute("selectable") != "false", + image: cell.getAttribute("src"), + mode: cell.hasAttribute("mode") ? parseInt(cell.getAttribute("mode")) : 3 }); + } + + var descendants = treeitem.lastChild; + var children = (treerow == descendants) ? null : + convertDOMtoTreeRowInfo(descendants, level + 1, rowidx); + obj.rows.push({ cells: cellInfo, + properties: treerow.getAttribute("properties"), + container: treeitem.getAttribute("container") == "true", + separator: treeitem.localName == "treeseparator", + children: children, + level: level, + parent: parentidx }); + } + } + + return obj; +} diff --git a/toolkit/content/tests/widgets/video.ogg b/toolkit/content/tests/widgets/video.ogg Binary files differnew file mode 100644 index 0000000000..ac7ece3519 --- /dev/null +++ b/toolkit/content/tests/widgets/video.ogg diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1-ref.html b/toolkit/content/tests/widgets/videocontrols_direction-1-ref.html new file mode 100644 index 0000000000..1f7e76a7d0 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1a.html b/toolkit/content/tests/widgets/videocontrols_direction-1a.html new file mode 100644 index 0000000000..a4d3546294 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1a.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html dir="rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1b.html b/toolkit/content/tests/widgets/videocontrols_direction-1b.html new file mode 100644 index 0000000000..a14b11d5ff --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1b.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html style="direction: rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1c.html b/toolkit/content/tests/widgets/videocontrols_direction-1c.html new file mode 100644 index 0000000000..0885ebd893 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1c.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="direction: rtl"> +<video controls preload="none" id="av" source="audio.wav"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1d.html b/toolkit/content/tests/widgets/videocontrols_direction-1d.html new file mode 100644 index 0000000000..a39accec72 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1d.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<video controls preload="none" id="av" source="audio.wav" dir="rtl"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-1e.html b/toolkit/content/tests/widgets/videocontrols_direction-1e.html new file mode 100644 index 0000000000..25e7c2c1f9 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-1e.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<video controls preload="none" id="av" source="audio.wav" style="direction: rtl;"></video> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2-ref.html b/toolkit/content/tests/widgets/videocontrols_direction-2-ref.html new file mode 100644 index 0000000000..630177883c --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2-ref.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2a.html b/toolkit/content/tests/widgets/videocontrols_direction-2a.html new file mode 100644 index 0000000000..2e40cdc1a7 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2a.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html dir="rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2b.html b/toolkit/content/tests/widgets/videocontrols_direction-2b.html new file mode 100644 index 0000000000..2e4dadb6ff --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2b.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html style="direction: rtl"> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2c.html b/toolkit/content/tests/widgets/videocontrols_direction-2c.html new file mode 100644 index 0000000000..a43b03e8f9 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2c.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="direction: rtl"> +<audio controls preload="none" id="av" source="audio.wav"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2d.html b/toolkit/content/tests/widgets/videocontrols_direction-2d.html new file mode 100644 index 0000000000..52d56f1ccd --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2d.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<audio controls preload="none" id="av" source="audio.wav" dir="rtl"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction-2e.html b/toolkit/content/tests/widgets/videocontrols_direction-2e.html new file mode 100644 index 0000000000..58bc30e2b3 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction-2e.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML> +<html> +<head> +<link rel="stylesheet" type="text/css" href="videomask.css"> +</head> +<body style="text-align: right;"> +<audio controls preload="none" id="av" source="audio.wav" style="direction: rtl;"></audio> +<div id="mask"></div> +</body> +</html> diff --git a/toolkit/content/tests/widgets/videocontrols_direction_test.js b/toolkit/content/tests/widgets/videocontrols_direction_test.js new file mode 100644 index 0000000000..8ad76c0644 --- /dev/null +++ b/toolkit/content/tests/widgets/videocontrols_direction_test.js @@ -0,0 +1,90 @@ +var RemoteCanvas = function(url, id) { + this.url = url; + this.id = id; + this.snapshot = null; +}; + +RemoteCanvas.CANVAS_WIDTH = 200; +RemoteCanvas.CANVAS_HEIGHT = 200; + +RemoteCanvas.prototype.compare = function(otherCanvas, expected) { + return compareSnapshots(this.snapshot, otherCanvas.snapshot, expected)[0]; +} + +RemoteCanvas.prototype.load = function(callback) { + var iframe = document.createElement("iframe"); + iframe.id = this.id + "-iframe"; + iframe.width = RemoteCanvas.CANVAS_WIDTH + "px"; + iframe.height = RemoteCanvas.CANVAS_HEIGHT + "px"; + iframe.src = this.url; + var me = this; + iframe.addEventListener("load", function() { + info("iframe loaded"); + var m = iframe.contentDocument.getElementById("av"); + m.addEventListener("suspend", function(aEvent) { + m.removeEventListener("suspend", arguments.callee, false); + setTimeout(function() { + me.remotePageLoaded(callback); + }, 0); + }, false); + m.src = m.getAttribute("source"); + }, false); + window.document.body.appendChild(iframe); +}; + +RemoteCanvas.prototype.remotePageLoaded = function(callback) { + var ldrFrame = document.getElementById(this.id + "-iframe"); + this.snapshot = snapshotWindow(ldrFrame.contentWindow); + this.snapshot.id = this.id + "-canvas"; + window.document.body.appendChild(this.snapshot); + callback(this); +}; + +RemoteCanvas.prototype.cleanup = function() { + var iframe = document.getElementById(this.id + "-iframe"); + iframe.parentNode.removeChild(iframe); + var canvas = document.getElementById(this.id + "-canvas"); + canvas.parentNode.removeChild(canvas); +}; + +function runTest(index) { + var canvases = []; + function testCallback(canvas) { + canvases.push(canvas); + + if (canvases.length == 2) { // when both canvases are loaded + var expectedEqual = currentTest.op == "=="; + var result = canvases[0].compare(canvases[1], expectedEqual); + ok(result, "Rendering of reftest " + currentTest.test + " should " + + (expectedEqual ? "not " : "") + "be different to the reference"); + + if (result) { + canvases[0].cleanup(); + canvases[1].cleanup(); + } + else { + info("Snapshot of canvas 1: " + canvases[0].snapshot.toDataURL()); + info("Snapshot of canvas 2: " + canvases[1].snapshot.toDataURL()); + } + + if (index < tests.length - 1) + runTest(index + 1); + else + SimpleTest.finish(); + } + } + + var currentTest = tests[index]; + var testCanvas = new RemoteCanvas(currentTest.test, "test-" + index); + testCanvas.load(testCallback); + + var refCanvas = new RemoteCanvas(currentTest.ref, "ref-" + index); + refCanvas.load(testCallback); +} + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestCompleteLog(); + +window.addEventListener("load", function() { + SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, function() { runTest(0); }); +}, true); diff --git a/toolkit/content/tests/widgets/videomask.css b/toolkit/content/tests/widgets/videomask.css new file mode 100644 index 0000000000..066d441388 --- /dev/null +++ b/toolkit/content/tests/widgets/videomask.css @@ -0,0 +1,23 @@ +html, body { + margin: 0; + padding: 0; +} + +audio, video { + width: 140px; + height: 100px; + background-color: black; +} + +/** + * Create a mask for the video direction tests which covers up the throbber. + */ +#mask { + position: absolute; + z-index: 3; + width: 140px; + height: 72px; + background-color: green; + top: 0; + right: 0; +} diff --git a/toolkit/content/tests/widgets/window_menubar.xul b/toolkit/content/tests/widgets/window_menubar.xul new file mode 100644 index 0000000000..b7669e0b37 --- /dev/null +++ b/toolkit/content/tests/widgets/window_menubar.xul @@ -0,0 +1,820 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<!-- the condition in the focus event handler is because pressing Tab + unfocuses and refocuses the window on Windows --> + +<window title="Popup Tests" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="popup_shared.js"></script> + +<!-- + Need to investigate these tests a bit more. Some of the accessibility events + are firing multiple times or in different orders in different circumstances. + Note that this was also the case before bug 279703. + --> + +<hbox style="margin-left: 275px; margin-top: 275px;"> +<menubar id="menubar"> + <menu id="filemenu" label="File" accesskey="F"> + <menupopup id="filepopup"> + <menuitem id="item1" label="Open" accesskey="O"/> + <menuitem id="item2" label="Save" accesskey="S"/> + <menuitem id="item3" label="Close" accesskey="C"/> + </menupopup> + </menu> + <menu id="secretmenu" label="Secret Menu" accesskey="S" disabled="true"> + <menupopup> + <menuitem label="Secret Command" accesskey="S"/> + </menupopup> + </menu> + <menu id="editmenu" label="Edit" accesskey="E"> + <menupopup id="editpopup"> + <menuitem id="cut" label="Cut" accesskey="t" disabled="true"/> + <menuitem id="copy" label="Copy" accesskey="C"/> + <menuitem id="paste" label="Paste" accesskey="P"/> + </menupopup> + </menu> + <menu id="viewmenu" label="View" accesskey="V"> + <menupopup id="viewpopup"> + <menu id="toolbar" label="Toolbar" accesskey="T"> + <menupopup id="toolbarpopup"> + <menuitem id="navigation" label="Navigation" accesskey="N" disabled="true"/> + <menuitem label="Bookmarks" accesskey="B" disabled="true"/> + </menupopup> + </menu> + <menuitem label="Status Bar" accesskey="S"/> + <menu label="Sidebar" accesskey="d"> + <menupopup> + <menuitem label="Bookmarks" accesskey="B"/> + <menuitem label="History" accesskey="H"/> + </menupopup> + </menu> + </menupopup> + </menu> + <menu id="helpmenu" label="Help" accesskey="H"> + <menupopup id="helppopup" > + <label value="Unselectable"/> + <menuitem id="contents" label="Contents" accesskey="C"/> + <menuitem label="More Info" accesskey="I"/> + <menuitem id="amenu" label="A Menu" accesskey="M"/> + <menuitem label="Another Menu"/> + <menuitem id="one" label="One"/> + <menu id="only" label="Only Menu"> + <menupopup> + <menuitem label="Test Submenu"/> + </menupopup> + </menu> + <menuitem label="Second Menu"/> + <menuitem id="other" disabled="true" label="Other Menu"/> + <menuitem id="third" label="Third Menu"/> + <menuitem label="One Other Menu"/> + <label value="Unselectable"/> + <menuitem id="about" label="About" accesskey="A"/> + </menupopup> + </menu> +</menubar> +</hbox> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +window.opener.SimpleTest.waitForFocus(function () { + gFilePopup = document.getElementById("filepopup"); + var filemenu = document.getElementById("filemenu"); + filemenu.focus(); + is(filemenu.openedWithKey, false, "initial openedWithKey"); + startPopupTests(popupTests); +}, window); + +// On Linux, the first menu opens when F10 is pressed, but on other platforms +// the menubar is focused but no menu is opened. This means that different events +// fire. +function pressF10Events() +{ + return navigator.platform.indexOf("Linux") >= 0 ? + [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu", "popupshowing filepopup", "DOMMenuItemActive item1", "popupshown filepopup"] : + [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ]; +} + +function closeAfterF10Events(extraInactive) +{ + if (navigator.platform.indexOf("Linux") >= 0) { + var events = [ "popuphiding filepopup", "popuphidden filepopup", "DOMMenuItemInactive item1", + "DOMMenuInactive filepopup", "DOMMenuBarInactive menubar", + "DOMMenuItemInactive filemenu" ]; + if (extraInactive) + events.push("DOMMenuItemInactive filemenu"); + return events; + } + + return [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ]; +} + +var popupTests = [ +{ + testname: "press on menu", + events: [ "popupshowing filepopup", "DOMMenuBarActive menubar", + "DOMMenuItemActive filemenu", "popupshown filepopup" ], + test: function() { synthesizeMouse(document.getElementById("filemenu"), 8, 8, { }); }, + result: function (testname) { + checkActive(gFilePopup, "", testname); + checkOpen("filemenu", testname); + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + // check that pressing cursor down while there is no selection + // highlights the first item + testname: "cursor down no selection", + events: [ "DOMMenuItemActive item1" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { checkActive(gFilePopup, "item1", testname); } +}, +{ + // check that pressing cursor up wraps and highlights the last item + testname: "cursor up wrap", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive item3" ], + test: function() { sendKey("UP"); }, + result: function(testname) { checkActive(gFilePopup, "item3", testname); } +}, +{ + // check that pressing cursor down wraps and highlights the first item + testname: "cursor down wrap", + events: [ "DOMMenuItemInactive item3", "DOMMenuItemActive item1" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { checkActive(gFilePopup, "item1", testname); } +}, +{ + // check that pressing cursor down highlights the second item + testname: "cursor down", + events: [ "DOMMenuItemInactive item1", "DOMMenuItemActive item2" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { checkActive(gFilePopup, "item2", testname); } +}, +{ + // check that pressing cursor up highlights the second item + testname: "cursor up", + events: [ "DOMMenuItemInactive item2", "DOMMenuItemActive item1" ], + test: function() { sendKey("UP"); }, + result: function(testname) { checkActive(gFilePopup, "item1", testname); } +}, + +{ + // cursor right should skip the disabled menu and move to the edit menu + testname: "cursor right skip disabled", + events: function() { + var elist = [ + // the file menu gets deactivated, the file menu gets hidden, then + // the edit menu is activated + "DOMMenuItemInactive filemenu", "DOMMenuItemActive editmenu", + "popuphiding filepopup", "popuphidden filepopup", + // the popupshowing event gets fired when showing the edit menu. + // The item from the file menu doesn't get deactivated until the + // next item needs to be selected + "popupshowing editpopup", "DOMMenuItemInactive item1", + // not sure why the menu inactivated event is firing so late + "DOMMenuInactive filepopup" + ]; + // finally, the first item is activated and popupshown is fired. + // On Windows, don't skip disabled items. + if (navigator.platform.indexOf("Win") == 0) + elist.push("DOMMenuItemActive cut"); + else + elist.push("DOMMenuItemActive copy"); + elist.push("popupshown editpopup"); + return elist; + }, + test: function() { sendKey("RIGHT"); }, + result: function(testname) { + var expected = (navigator.platform.indexOf("Win") == 0) ? "cut" : "copy"; + checkActive(document.getElementById("editpopup"), expected, testname); + checkClosed("filemenu", testname); + checkOpen("editmenu", testname); + is(document.getElementById("editmenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + // on Windows, a disabled item is selected, so pressing RETURN should close + // the menu but not fire a command event + testname: "enter on disabled", + events: function() { + if (navigator.platform.indexOf("Win") == 0) + return [ "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive cut", "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", "DOMMenuItemInactive editmenu" ]; + else + return [ "DOMMenuItemInactive copy", "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", "DOMMenuItemInactive editmenu", + "command copy", "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive copy" ]; + }, + test: function() { sendKey("RETURN"); }, + result: function(testname) { + checkClosed("editmenu", testname); + is(document.getElementById("editmenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + // pressing Alt + a key should open the corresponding menu + testname: "open with accelerator", + events: function() { + return [ "DOMMenuBarActive menubar", + "popupshowing viewpopup", "DOMMenuItemActive viewmenu", + "DOMMenuItemActive toolbar", "popupshown viewpopup" ]; + }, + test: function() { synthesizeKey("V", { altKey: true }); }, + result: function(testname) { + checkOpen("viewmenu", testname); + is(document.getElementById("viewmenu").openedWithKey, true, testname + " openedWithKey"); + } +}, +{ + // open the submenu with the cursor right key + testname: "open submenu with cursor right", + events: function() { + // on Windows, the disabled 'navigation' item can stll be highlihted + if (navigator.platform.indexOf("Win") == 0) + return [ "popupshowing toolbarpopup", "DOMMenuItemActive navigation", + "popupshown toolbarpopup" ]; + else + return [ "popupshowing toolbarpopup", "popupshown toolbarpopup" ]; + }, + test: function() { sendKey("RIGHT"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkOpen("toolbar", testname); + } +}, +{ + // close the submenu with the cursor left key + testname: "close submenu with cursor left", + events: function() { + if (navigator.platform.indexOf("Win") == 0) + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuItemInactive navigation", "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + else + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + }, + test: function() { sendKey("LEFT"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkClosed("toolbar", testname); + } +}, +{ + // open the submenu with the enter key + testname: "open submenu with enter", + events: function() { + // on Windows, the disabled 'navigation' item can stll be highlighted + if (navigator.platform.indexOf("Win") == 0) + return [ "popupshowing toolbarpopup", "DOMMenuItemActive navigation", + "popupshown toolbarpopup" ]; + else + return [ "popupshowing toolbarpopup", "popupshown toolbarpopup" ]; + }, + test: function() { sendKey("RETURN"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkOpen("toolbar", testname); + }, +}, +{ + // close the submenu with the escape key + testname: "close submenu with escape", + events: function() { + if (navigator.platform.indexOf("Win") == 0) + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuItemInactive navigation", "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + else + return [ "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "DOMMenuInactive toolbarpopup", + "DOMMenuItemActive toolbar" ]; + }, + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkClosed("toolbar", testname); + }, +}, +{ + // open the submenu with the enter key again + testname: "open submenu with enter again", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + // on Windows, the disabled 'navigation' item can stll be highlighted + if (navigator.platform.indexOf("Win") == 0) + return [ "popupshowing toolbarpopup", "DOMMenuItemActive navigation", + "popupshown toolbarpopup" ]; + else + return [ "popupshowing toolbarpopup", "popupshown toolbarpopup" ]; + }, + test: function() { sendKey("RETURN"); }, + result: function(testname) { + checkOpen("viewmenu", testname); + checkOpen("toolbar", testname); + }, +}, +{ + // while a submenu is open, switch to the next toplevel menu with the cursor right key + testname: "while a submenu is open, switch to the next menu with the cursor right", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuItemInactive viewmenu", "DOMMenuItemActive helpmenu", + "popuphiding toolbarpopup", "popuphidden toolbarpopup", + "popuphiding viewpopup", "popuphidden viewpopup", + "popupshowing helppopup", "DOMMenuItemInactive navigation", + "DOMMenuInactive toolbarpopup", "DOMMenuItemInactive toolbar", + "DOMMenuInactive viewpopup", "DOMMenuItemActive contents", + "popupshown helppopup" ], + test: function() { sendKey("RIGHT"); }, + result: function(testname) { + checkOpen("helpmenu", testname); + checkClosed("toolbar", testname); + checkClosed("viewmenu", testname); + } +}, +{ + // close the main menu with the escape key + testname: "close menubar menu with escape", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "popuphiding helppopup", "popuphidden helppopup", + "DOMMenuItemInactive contents", "DOMMenuInactive helppopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive helpmenu" ], + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { checkClosed("viewmenu", testname); }, +}, +{ + // close the main menu with the escape key + testname: "close menubar menu with escape", + condition: function() { return (navigator.platform.indexOf("Win") != 0) }, + events: [ "popuphiding viewpopup", "popuphidden viewpopup", + "DOMMenuItemInactive toolbar", "DOMMenuInactive viewpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive viewmenu" ], + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { checkClosed("viewmenu", testname); }, +}, +{ + // Pressing Alt should highlight the first menu but not open it, + // but it should be ignored if the alt keydown event is consumed. + testname: "alt shouldn't activate menubar if keydown event is consumed", + test: function() { + document.addEventListener("keydown", function (aEvent) { + document.removeEventListener("keydown", arguments.callee, true); + aEvent.preventDefault(); + }, true); + sendKey("ALT"); + }, + result: function(testname) { + ok(!document.getElementById("filemenu").openedWithKey, testname); + checkClosed("filemenu", testname); + }, +}, +{ + // Pressing Alt should highlight the first menu but not open it, + // but it should be ignored if the alt keyup event is consumed. + testname: "alt shouldn't activate menubar if keyup event is consumed", + test: function() { + document.addEventListener("keyup", function (aEvent) { + document.removeEventListener("keyup", arguments.callee, true); + aEvent.preventDefault(); + }, true); + sendKey("ALT"); + }, + result: function(testname) { + ok(!document.getElementById("filemenu").openedWithKey, testname); + checkClosed("filemenu", testname); + }, +}, +{ + // Pressing Alt should highlight the first menu but not open it. + testname: "alt to activate menubar", + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("ALT"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, true, testname + " openedWithKey"); + checkClosed("filemenu", testname); + }, +}, +{ + // pressing cursor left should select the previous menu but not open it + testname: "cursor left on active menubar", + events: [ "DOMMenuItemInactive filemenu", "DOMMenuItemActive helpmenu" ], + test: function() { sendKey("LEFT"); }, + result: function(testname) { checkClosed("helpmenu", testname); }, +}, +{ + // pressing cursor right should select the previous menu but not open it + testname: "cursor right on active menubar", + events: [ "DOMMenuItemInactive helpmenu", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("RIGHT"); }, + result: function(testname) { checkClosed("filemenu", testname); }, +}, +{ + // pressing a character should act as an accelerator and open the menu + testname: "accelerator on active menubar", + events: [ "popupshowing helppopup", + "DOMMenuItemInactive filemenu", "DOMMenuItemActive helpmenu", + "DOMMenuItemActive contents", "popupshown helppopup" ], + test: function() { sendChar("h"); }, + result: function(testname) { + checkOpen("helpmenu", testname); + is(document.getElementById("helpmenu").openedWithKey, true, testname + " openedWithKey"); + }, +}, +{ + // check that pressing cursor up skips non menuitems + testname: "cursor up wrap", + events: [ "DOMMenuItemInactive contents", "DOMMenuItemActive about" ], + test: function() { sendKey("UP"); }, + result: function(testname) { } +}, +{ + // check that pressing cursor down skips non menuitems + testname: "cursor down wrap", + events: [ "DOMMenuItemInactive about", "DOMMenuItemActive contents" ], + test: function() { sendKey("DOWN"); }, + result: function(testname) { } +}, +{ + // check that pressing a menuitem's accelerator selects it + testname: "menuitem accelerator", + events: [ "DOMMenuItemInactive contents", "DOMMenuItemActive amenu", + "DOMMenuItemInactive amenu", "DOMMenuInactive helppopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive helpmenu", + "DOMMenuItemInactive helpmenu", + "command amenu", "popuphiding helppopup", "popuphidden helppopup", + "DOMMenuItemInactive amenu", + ], + test: function() { sendChar("m"); }, + result: function(testname) { checkClosed("helpmenu", testname); } +}, +{ + // pressing F10 should highlight the first menu. On Linux, the menu is opened. + testname: "F10 to activate menubar", + events: pressF10Events(), + test: function() { sendKey("F10"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, true, testname + " openedWithKey"); + if (navigator.platform.indexOf("Linux") >= 0) + checkOpen("filemenu", testname); + else + checkClosed("filemenu", testname); + }, +}, +{ + // pressing cursor left then down should open a menu + testname: "cursor down on menu", + events: (navigator.platform.indexOf("Linux") >= 0) ? + [ "DOMMenuItemInactive filemenu", "DOMMenuItemActive helpmenu", + // This is in a different order than the + // "accelerator on active menubar" because menus opened from a + // shortcut key are fired asynchronously + "popuphiding filepopup", "popuphidden filepopup", + "popupshowing helppopup", "DOMMenuItemInactive item1", + "DOMMenuItemActive item2", "DOMMenuItemInactive item2", + "DOMMenuInactive filepopup", "DOMMenuItemActive contents", + "popupshown helppopup" ] : + [ "popupshowing helppopup", "DOMMenuItemInactive filemenu", + "DOMMenuItemActive helpmenu", + // This is in a different order than the + // "accelerator on active menubar" because menus opened from a + // shortcut key are fired asynchronously + "DOMMenuItemActive contents", "popupshown helppopup" ], + test: function() { sendKey("LEFT"); sendKey("DOWN"); }, + result: function(testname) { + is(document.getElementById("helpmenu").openedWithKey, true, testname + " openedWithKey"); + } +}, +{ + // pressing a letter that doesn't correspond to an accelerator. The menu + // should not close because there is more than one item corresponding to + // that letter + testname: "menuitem with no accelerator", + events: [ "DOMMenuItemInactive contents", "DOMMenuItemActive one" ], + test: function() { sendChar("o"); }, + result: function(testname) { checkOpen("helpmenu", testname); } +}, +{ + // pressing the letter again should select the next one that starts with + // that letter + testname: "menuitem with no accelerator again", + events: [ "DOMMenuItemInactive one", "DOMMenuItemActive only" ], + test: function() { sendChar("o"); }, + result: function(testname) { + // 'only' is a menu but it should not be open + checkOpen("helpmenu", testname); + checkClosed("only", testname); + } +}, +{ + // pressing the letter again when the next item is disabled should still + // select the disabled item + testname: "menuitem with no accelerator disabled", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuItemInactive only", "DOMMenuItemActive other" ], + test: function() { sendChar("o"); }, + result: function(testname) { } +}, +{ + // when only one menuitem starting with that letter exists, it should be + // selected and the menu closed + testname: "menuitem with no accelerator single", + events: function() { + var elist = [ "DOMMenuItemInactive other", "DOMMenuItemActive third", + "DOMMenuItemInactive third", "DOMMenuInactive helppopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive helpmenu", + "DOMMenuItemInactive helpmenu", + "command third", "popuphiding helppopup", + "popuphidden helppopup", "DOMMenuItemInactive third", + ]; + if (navigator.platform.indexOf("Win") == -1) + elist[0] = "DOMMenuItemInactive only"; + return elist; + }, + test: function() { sendChar("t"); }, + result: function(testname) { checkClosed("helpmenu", testname); } +}, +{ + // pressing F10 should highlight the first menu but not open it + testname: "F10 to activate menubar again", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("F10"); }, + result: function(testname) { checkClosed("filemenu", testname); }, +}, +{ + // pressing an accelerator for a disabled item should deactivate the menubar + testname: "accelerator for disabled menu", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuItemInactive filemenu", "DOMMenuBarInactive menubar" ], + test: function() { sendChar("s"); }, + result: function(testname) { + checkClosed("secretmenu", testname); + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + }, +}, +{ + testname: "press on disabled menu", + test: function() { + synthesizeMouse(document.getElementById("secretmenu"), 8, 8, { }); + }, + result: function (testname) { + checkClosed("secretmenu", testname); + } +}, +{ + testname: "press on second menu with shift", + events: [ "popupshowing editpopup", "DOMMenuBarActive menubar", + "DOMMenuItemActive editmenu", "popupshown editpopup" ], + test: function() { + synthesizeMouse(document.getElementById("editmenu"), 8, 8, { shiftKey : true }); + }, + result: function (testname) { + checkOpen("editmenu", testname); + checkActive(document.getElementById("menubar"), "editmenu", testname); + } +}, +{ + testname: "press on disabled menuitem", + test: function() { + synthesizeMouse(document.getElementById("cut"), 8, 8, { }); + }, + result: function (testname) { + checkOpen("editmenu", testname); + } +}, +{ + testname: "press on menuitem", + events: [ "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", + "DOMMenuItemInactive editmenu", + "command copy", "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive copy", + ], + test: function() { + synthesizeMouse(document.getElementById("copy"), 8, 8, { }); + }, + result: function (testname) { + checkClosed("editmenu", testname); + } +}, +{ + // this test ensures that the menu can still be opened by clicking after selecting + // a menuitem from the menu. See bug 399350. + testname: "press on menu after menuitem selected", + events: [ "popupshowing editpopup", "DOMMenuBarActive menubar", + "DOMMenuItemActive editmenu", "popupshown editpopup" ], + test: function() { synthesizeMouse(document.getElementById("editmenu"), 8, 8, { }); }, + result: function (testname) { + checkActive(document.getElementById("editpopup"), "", testname); + checkOpen("editmenu", testname); + } +}, +{ // try selecting a different command + testname: "press on menuitem again", + events: [ "DOMMenuInactive editpopup", + "DOMMenuBarInactive menubar", + "DOMMenuItemInactive editmenu", + "DOMMenuItemInactive editmenu", + "command paste", "popuphiding editpopup", "popuphidden editpopup", + "DOMMenuItemInactive paste", + ], + test: function() { + synthesizeMouse(document.getElementById("paste"), 8, 8, { }); + }, + result: function (testname) { + checkClosed("editmenu", testname); + } +}, +{ + testname: "F10 to activate menubar for tab deactivation", + events: pressF10Events(), + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with tab key", + events: closeAfterF10Events(true), + test: function() { sendKey("TAB"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "F10 to activate menubar for escape deactivation", + events: pressF10Events(), + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with escape key", + events: closeAfterF10Events(false), + test: function() { sendKey("ESCAPE"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "F10 to activate menubar for f10 deactivation", + events: pressF10Events(), + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with f10 key", + events: closeAfterF10Events(true), + test: function() { sendKey("F10"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "F10 to activate menubar for alt deactivation", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ], + test: function() { sendKey("F10"); }, +}, +{ + testname: "Deactivate menubar with alt key", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ], + test: function() { sendKey("ALT"); }, + result: function(testname) { + is(document.getElementById("filemenu").openedWithKey, false, testname + " openedWithKey"); + } +}, +{ + testname: "Don't activate menubar with mousedown during alt key auto-repeat", + test: function() { + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeMouse(document.getElementById("menubar"), 8, -30, { type: "mousedown", altKey: true }); + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeMouse(document.getElementById("menubar"), 8, -30, { type: "mouseup", altKey: true }); + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeKey("VK_ALT", { type: "keyup" }); + }, + result: function (testname) { + checkActive(document.getElementById("menubar"), "", testname); + } +}, + +{ + testname: "Open menu and press alt key by itself - open menu", + events: [ "DOMMenuBarActive menubar", + "popupshowing filepopup", "DOMMenuItemActive filemenu", + "DOMMenuItemActive item1", "popupshown filepopup" ], + test: function() { synthesizeKey("F", { altKey: true }); }, + result: function (testname) { + checkOpen("filemenu", testname); + } +}, +{ + testname: "Open menu and press alt key by itself - close menu", + events: [ "popuphiding filepopup", "popuphidden filepopup", + "DOMMenuItemInactive item1", "DOMMenuInactive filepopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu", + "DOMMenuItemInactive filemenu" ], + test: function() { + synthesizeKey("VK_ALT", { }); + }, + result: function (testname) { + checkClosed("filemenu", testname); + } +}, + +// Fllowing 4 tests are a test of bug 616797, don't insert any new tests +// between them. +{ + testname: "Open file menu by accelerator", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarActive menubar", "popupshowing filepopup", + "DOMMenuItemActive filemenu", "DOMMenuItemActive item1", + "popupshown filepopup" ]; + }, + test: function() { + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeKey("F", { altKey: true }); + synthesizeKey("VK_ALT", { type: "keyup" }); + } +}, +{ + testname: "Close file menu by click at outside of popup menu", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "popuphiding filepopup", "popuphidden filepopup", + "DOMMenuItemInactive item1", "DOMMenuInactive filepopup", + "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu", + "DOMMenuItemInactive filemenu" ]; + }, + test: function() { + // XXX hidePopup() causes DOMMenuItemInactive event to be fired twice. + document.getElementById("filepopup").hidePopup(); + } +}, +{ + testname: "Alt keydown set focus the menubar", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ]; + }, + test: function() { + sendKey("ALT"); + }, + result: function (testname) { + checkClosed("filemenu", testname); + } +}, +{ + testname: "unset focus the menubar", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarInactive menubar", "DOMMenuItemInactive filemenu" ]; + }, + test: function() { + sendKey("ALT"); + } +}, + +// bug 625151 +{ + testname: "Alt key state before deactivating the window shouldn't prevent " + + "next Alt key handling", + condition: function() { return (navigator.platform.indexOf("Win") == 0) }, + events: function() { + return [ "DOMMenuBarActive menubar", "DOMMenuItemActive filemenu" ]; + }, + test: function() { + synthesizeKey("VK_ALT", { type: "keydown" }); + synthesizeKey("VK_TAB", { type: "keydown" }); // cancels the Alt key + var thisWindow = window; + var newWindow = + window.open("data:text/html,", "_blank", "width=100,height=100"); + newWindow.addEventListener("focus", function () { + newWindow.removeEventListener("focus", arguments.callee, false); + thisWindow.addEventListener("focus", function () { + thisWindow.removeEventListener("focus", arguments.callee, false); + setTimeout(function () { + sendKey("ALT", thisWindow); + }, 0); + }, false); + newWindow.close(); + thisWindow.focus(); + }, false); + } +} + +]; + +]]> +</script> + +</window> |