mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 07:15:46 +00:00
Merge mozilla-central to mozilla-inbound. a=merge
This commit is contained in:
commit
6a888dd9ac
@ -5479,10 +5479,19 @@ var TabContextMenu = {
|
|||||||
let allSelectedTabsAdjacent = selectedTabs.every((element, index, array) => {
|
let allSelectedTabsAdjacent = selectedTabs.every((element, index, array) => {
|
||||||
return array.length > index + 1 ? element._tPos + 1 == array[index + 1]._tPos : true;
|
return array.length > index + 1 ? element._tPos + 1 == array[index + 1]._tPos : true;
|
||||||
});
|
});
|
||||||
contextMoveTabToEnd.disabled = selectedTabs[selectedTabs.length - 1]._tPos == gBrowser.visibleTabs.length - 1 &&
|
let contextTabIsSelected = this.contextTab.multiselected;
|
||||||
|
let visibleTabs = gBrowser.visibleTabs;
|
||||||
|
let lastVisibleTab = visibleTabs[visibleTabs.length - 1];
|
||||||
|
let tabsToMove = contextTabIsSelected ? selectedTabs : [this.contextTab];
|
||||||
|
let lastTabToMove = tabsToMove[tabsToMove.length - 1];
|
||||||
|
let isLastPinnedTab = lastTabToMove.pinned &&
|
||||||
|
(!lastTabToMove.nextElementSibling || !lastTabToMove.nextElementSibling.pinned);
|
||||||
|
contextMoveTabToEnd.disabled = (lastTabToMove == lastVisibleTab || isLastPinnedTab) &&
|
||||||
allSelectedTabsAdjacent;
|
allSelectedTabsAdjacent;
|
||||||
let contextMoveTabToStart = document.getElementById("context_moveToStart");
|
let contextMoveTabToStart = document.getElementById("context_moveToStart");
|
||||||
contextMoveTabToStart.disabled = selectedTabs[0]._tPos == 0 && allSelectedTabsAdjacent;
|
let isFirstTab = tabsToMove[0] == visibleTabs[0] ||
|
||||||
|
tabsToMove[0] == visibleTabs[gBrowser._numPinnedTabs];
|
||||||
|
contextMoveTabToStart.disabled = isFirstTab && allSelectedTabsAdjacent;
|
||||||
|
|
||||||
// Only one of "Duplicate Tab"/"Duplicate Tabs" should be visible.
|
// Only one of "Duplicate Tab"/"Duplicate Tabs" should be visible.
|
||||||
document.getElementById("context_duplicateTab").hidden = multiselectionContext;
|
document.getElementById("context_duplicateTab").hidden = multiselectionContext;
|
||||||
|
@ -32,6 +32,7 @@ support-files =
|
|||||||
[browser_multiselect_tabs_event.js]
|
[browser_multiselect_tabs_event.js]
|
||||||
[browser_multiselect_tabs_move_to_another_window_drag.js]
|
[browser_multiselect_tabs_move_to_another_window_drag.js]
|
||||||
[browser_multiselect_tabs_move_to_new_window_contextmenu.js]
|
[browser_multiselect_tabs_move_to_new_window_contextmenu.js]
|
||||||
|
[browser_multiselect_tabs_move.js]
|
||||||
[browser_multiselect_tabs_mute_unmute.js]
|
[browser_multiselect_tabs_mute_unmute.js]
|
||||||
[browser_multiselect_tabs_open_related.js]
|
[browser_multiselect_tabs_open_related.js]
|
||||||
[browser_multiselect_tabs_pin_unpin.js]
|
[browser_multiselect_tabs_pin_unpin.js]
|
||||||
|
200
browser/base/content/test/tabs/browser_multiselect_tabs_move.js
Normal file
200
browser/base/content/test/tabs/browser_multiselect_tabs_move.js
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
|
||||||
|
|
||||||
|
add_task(async function setPref() {
|
||||||
|
await SpecialPowers.pushPrefEnv({
|
||||||
|
set: [[PREF_MULTISELECT_TABS, true]],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartEnabledClickedFromNonSelectedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
let tab3 = await addTab();
|
||||||
|
|
||||||
|
let tabs = [tab2, tab3];
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
await triggerClickOn(tab, {});
|
||||||
|
await triggerClickOn(tab2, { ctrlKey: true });
|
||||||
|
|
||||||
|
ok(tab.multiselected, "Tab is multiselected");
|
||||||
|
ok(tab2.multiselected, "Tab2 is multiselected");
|
||||||
|
|
||||||
|
updateTabContextMenu(tab3);
|
||||||
|
is(menuItemMoveStartTab.disabled, false, "Move Tab to Start is enabled");
|
||||||
|
|
||||||
|
for (let tabToRemove of tabs) {
|
||||||
|
BrowserTestUtils.removeTab(tabToRemove);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartDisabledFromFirstUnpinnedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
gBrowser.pinTab(tab);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab2);
|
||||||
|
is(menuItemMoveStartTab.disabled, true, "Move Tab to Start is disabled");
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(tab2);
|
||||||
|
gBrowser.unpinTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartDisabledFromFirstPinnedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
gBrowser.pinTab(tab);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveStartTab.disabled, true, "Move Tab to Start is disabled");
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(tab2);
|
||||||
|
gBrowser.unpinTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartDisabledFromOnlyTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveStartTab.disabled, true, "Move Tab to Start is disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartDisabledFromOnlyPinnedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
gBrowser.pinTab(tab);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveStartTab.disabled, true, "Move Tab to Start is disabled");
|
||||||
|
|
||||||
|
gBrowser.unpinTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartEnabledFromLastPinnedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
let tab3 = await addTab();
|
||||||
|
|
||||||
|
let tabs = [tab2, tab3];
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
gBrowser.pinTab(tab);
|
||||||
|
gBrowser.pinTab(tab2);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab2);
|
||||||
|
is(menuItemMoveStartTab.disabled, false, "Move Tab to Start is enabled");
|
||||||
|
|
||||||
|
for (let tabToRemove of tabs) {
|
||||||
|
BrowserTestUtils.removeTab(tabToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
gBrowser.unpinTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveStartDisabledFromFirstVisibleTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
|
||||||
|
let menuItemMoveStartTab = document.getElementById("context_moveToStart");
|
||||||
|
|
||||||
|
gBrowser.selectTabAtIndex(1);
|
||||||
|
gBrowser.hideTab(tab);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab2);
|
||||||
|
is(menuItemMoveStartTab.disabled, true, "Move Tab to Start is disabled");
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(tab2);
|
||||||
|
gBrowser.showTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveEndEnabledClickedFromNonSelectedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
let tab3 = await addTab();
|
||||||
|
|
||||||
|
let tabs = [tab2, tab3];
|
||||||
|
|
||||||
|
let menuItemMoveEndTab = document.getElementById("context_moveToEnd");
|
||||||
|
|
||||||
|
await triggerClickOn(tab2, {});
|
||||||
|
await triggerClickOn(tab3, { ctrlKey: true });
|
||||||
|
|
||||||
|
ok(tab2.multiselected, "Tab2 is multiselected");
|
||||||
|
ok(tab3.multiselected, "Tab3 is multiselected");
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveEndTab.disabled, false, "Move Tab to End is enabled");
|
||||||
|
|
||||||
|
for (let tabToRemove of tabs) {
|
||||||
|
BrowserTestUtils.removeTab(tabToRemove);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveEndDisabledFromLastPinnedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
let tab3 = await addTab();
|
||||||
|
|
||||||
|
let tabs = [tab2, tab3];
|
||||||
|
|
||||||
|
let menuItemMoveEndTab = document.getElementById("context_moveToEnd");
|
||||||
|
|
||||||
|
gBrowser.pinTab(tab);
|
||||||
|
gBrowser.pinTab(tab2);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab2);
|
||||||
|
is(menuItemMoveEndTab.disabled, true, "Move Tab to End is disabled");
|
||||||
|
|
||||||
|
for (let tabToRemove of tabs) {
|
||||||
|
BrowserTestUtils.removeTab(tabToRemove);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveEndDisabledFromLastVisibleTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
let tab2 = await addTab();
|
||||||
|
|
||||||
|
let menuItemMoveEndTab = document.getElementById("context_moveToEnd");
|
||||||
|
|
||||||
|
gBrowser.hideTab(tab2);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveEndTab.disabled, true, "Move Tab to End is disabled");
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(tab2);
|
||||||
|
gBrowser.showTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveEndDisabledFromOnlyTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
|
||||||
|
let menuItemMoveEndTab = document.getElementById("context_moveToEnd");
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveEndTab.disabled, true, "Move Tab to End is disabled");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function testMoveEndDisabledFromOnlyPinnedTab() {
|
||||||
|
let tab = gBrowser.selectedTab;
|
||||||
|
|
||||||
|
let menuItemMoveEndTab = document.getElementById("context_moveToEnd");
|
||||||
|
|
||||||
|
gBrowser.pinTab(tab);
|
||||||
|
|
||||||
|
updateTabContextMenu(tab);
|
||||||
|
is(menuItemMoveEndTab.disabled, true, "Move Tab to End is disabled");
|
||||||
|
|
||||||
|
gBrowser.unpinTab(tab);
|
||||||
|
});
|
@ -297,7 +297,7 @@ class FormAutofillSection {
|
|||||||
// Use case for multiple select is not considered here.
|
// Use case for multiple select is not considered here.
|
||||||
if (!option.selected) {
|
if (!option.selected) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
element.dispatchEvent(new element.ownerGlobal.UIEvent("input", {bubbles: true}));
|
element.dispatchEvent(new element.ownerGlobal.Event("input", {bubbles: true}));
|
||||||
element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
|
element.dispatchEvent(new element.ownerGlobal.Event("change", {bubbles: true}));
|
||||||
}
|
}
|
||||||
// Autofill highlight appears regardless if value is changed or not
|
// Autofill highlight appears regardless if value is changed or not
|
||||||
|
@ -113,7 +113,20 @@ function triggerAutofillAndCheckProfile(profile) {
|
|||||||
const element = document.getElementById(fieldName);
|
const element = document.getElementById(fieldName);
|
||||||
const expectingEvent = document.activeElement == element ? "DOMAutoComplete" : "change";
|
const expectingEvent = document.activeElement == element ? "DOMAutoComplete" : "change";
|
||||||
const checkFieldAutofilled = Promise.all([
|
const checkFieldAutofilled = Promise.all([
|
||||||
new Promise(resolve => element.addEventListener("input", resolve, {once: true})),
|
new Promise(resolve => element.addEventListener("input", (event) => {
|
||||||
|
if (element.tagName == "INPUT" && element.type == "text") {
|
||||||
|
ok(event instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface on ${element.tagName}`);
|
||||||
|
} else {
|
||||||
|
ok(event instanceof Event && !(event instanceof UIEvent),
|
||||||
|
`"input" event should be dispatched with Event interface on ${element.tagName}`);
|
||||||
|
}
|
||||||
|
is(event.cancelable, false,
|
||||||
|
`"input" event should be never cancelable on ${element.tagName}`);
|
||||||
|
is(event.bubbles, true,
|
||||||
|
`"input" event should always bubble on ${element.tagName}`);
|
||||||
|
resolve();
|
||||||
|
}, {once: true})),
|
||||||
new Promise(resolve => element.addEventListener(expectingEvent, resolve, {once: true})),
|
new Promise(resolve => element.addEventListener(expectingEvent, resolve, {once: true})),
|
||||||
]).then(() => checkFieldValue(element, value));
|
]).then(() => checkFieldValue(element, value));
|
||||||
|
|
||||||
|
@ -67,7 +67,15 @@ function checkIsFormCleared(patch = {}) {
|
|||||||
async function confirmClear(selector) {
|
async function confirmClear(selector) {
|
||||||
info("Await for clearing input");
|
info("Await for clearing input");
|
||||||
let promise = new Promise(resolve =>
|
let promise = new Promise(resolve =>
|
||||||
document.querySelector(selector).addEventListener("input", resolve, {once: true})
|
document.querySelector(selector).addEventListener("input", (event) => {
|
||||||
|
ok(event instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface');
|
||||||
|
is(event.cancelable, false,
|
||||||
|
'"input" event should be never cancelable');
|
||||||
|
is(event.bubbles, true,
|
||||||
|
'"input" event should always bubble');
|
||||||
|
resolve();
|
||||||
|
}, {once: true})
|
||||||
);
|
);
|
||||||
synthesizeKey("KEY_Enter");
|
synthesizeKey("KEY_Enter");
|
||||||
await promise;
|
await promise;
|
||||||
|
@ -47,8 +47,19 @@ let MOCK_STORAGE = [{
|
|||||||
function checkElementFilled(element, expectedvalue) {
|
function checkElementFilled(element, expectedvalue) {
|
||||||
return [
|
return [
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
element.addEventListener("input", function onInput() {
|
element.addEventListener("input", function onInput(event) {
|
||||||
ok(true, "Checking " + element.name + " field fires input event");
|
ok(true, "Checking " + element.name + " field fires input event");
|
||||||
|
if (element.tagName == "INPUT" && element.type == "text") {
|
||||||
|
ok(event instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface on ${element.name}`);
|
||||||
|
} else {
|
||||||
|
ok(event instanceof Event && !(event instanceof UIEvent),
|
||||||
|
`"input" event should be dispatched with Event interface on ${element.name}`);
|
||||||
|
}
|
||||||
|
is(event.cancelable, false,
|
||||||
|
`"input" event should be never cancelable on ${element.name}`);
|
||||||
|
is(event.bubbles, true,
|
||||||
|
`"input" event should always bubble on ${element.name}`);
|
||||||
resolve();
|
resolve();
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
}),
|
}),
|
||||||
|
@ -35,7 +35,7 @@ const {
|
|||||||
RUNTIMES,
|
RUNTIMES,
|
||||||
} = require("../constants");
|
} = require("../constants");
|
||||||
|
|
||||||
function inspectDebugTarget({ type, id, front }) {
|
function inspectDebugTarget(type, id) {
|
||||||
return async (_, getState) => {
|
return async (_, getState) => {
|
||||||
const runtime = getCurrentRuntime(getState().runtimes);
|
const runtime = getCurrentRuntime(getState().runtimes);
|
||||||
const { runtimeDetails, type: runtimeType } = runtime;
|
const { runtimeDetails, type: runtimeType } = runtime;
|
||||||
@ -54,9 +54,7 @@ function inspectDebugTarget({ type, id, front }) {
|
|||||||
}
|
}
|
||||||
case DEBUG_TARGETS.EXTENSION: {
|
case DEBUG_TARGETS.EXTENSION: {
|
||||||
if (runtimeType === RUNTIMES.NETWORK || runtimeType === RUNTIMES.USB) {
|
if (runtimeType === RUNTIMES.NETWORK || runtimeType === RUNTIMES.USB) {
|
||||||
// runtimeDetails.client is a ClientWrapper instance, here we need to go back
|
const devtoolsClient = runtimeDetails.clientWrapper.client;
|
||||||
// to the actual DevTools client. Confusion should be reduce after Bug 1506056.
|
|
||||||
const devtoolsClient = runtimeDetails.client.client;
|
|
||||||
await debugRemoteAddon(id, devtoolsClient);
|
await debugRemoteAddon(id, devtoolsClient);
|
||||||
} else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
|
} else if (runtimeType === RUNTIMES.THIS_FIREFOX) {
|
||||||
debugLocalAddon(id);
|
debugLocalAddon(id);
|
||||||
@ -65,6 +63,7 @@ function inspectDebugTarget({ type, id, front }) {
|
|||||||
}
|
}
|
||||||
case DEBUG_TARGETS.WORKER: {
|
case DEBUG_TARGETS.WORKER: {
|
||||||
// Open worker toolbox in new window.
|
// Open worker toolbox in new window.
|
||||||
|
const front = runtimeDetails.client.client.getActor(id);
|
||||||
gDevToolsBrowser.openWorkerToolbox(front);
|
gDevToolsBrowser.openWorkerToolbox(front);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -91,10 +90,10 @@ function installTemporaryExtension() {
|
|||||||
|
|
||||||
function pushServiceWorker(actor) {
|
function pushServiceWorker(actor) {
|
||||||
return async (_, getState) => {
|
return async (_, getState) => {
|
||||||
const client = getCurrentClient(getState().runtimes);
|
const clientWrapper = getCurrentClient(getState().runtimes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.request({ to: actor, type: "push" });
|
await clientWrapper.request({ to: actor, type: "push" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@ -103,10 +102,10 @@ function pushServiceWorker(actor) {
|
|||||||
|
|
||||||
function reloadTemporaryExtension(actor) {
|
function reloadTemporaryExtension(actor) {
|
||||||
return async (_, getState) => {
|
return async (_, getState) => {
|
||||||
const client = getCurrentClient(getState().runtimes);
|
const clientWrapper = getCurrentClient(getState().runtimes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.request({ to: actor, type: "reload" });
|
await clientWrapper.request({ to: actor, type: "reload" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@ -127,10 +126,10 @@ function requestTabs() {
|
|||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
dispatch({ type: REQUEST_TABS_START });
|
dispatch({ type: REQUEST_TABS_START });
|
||||||
|
|
||||||
const client = getCurrentClient(getState().runtimes);
|
const clientWrapper = getCurrentClient(getState().runtimes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { tabs } = await client.listTabs({ favicons: true });
|
const { tabs } = await clientWrapper.listTabs({ favicons: true });
|
||||||
|
|
||||||
dispatch({ type: REQUEST_TABS_SUCCESS, tabs });
|
dispatch({ type: REQUEST_TABS_SUCCESS, tabs });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -144,10 +143,10 @@ function requestExtensions() {
|
|||||||
dispatch({ type: REQUEST_EXTENSIONS_START });
|
dispatch({ type: REQUEST_EXTENSIONS_START });
|
||||||
|
|
||||||
const runtime = getCurrentRuntime(getState().runtimes);
|
const runtime = getCurrentRuntime(getState().runtimes);
|
||||||
const client = getCurrentClient(getState().runtimes);
|
const clientWrapper = getCurrentClient(getState().runtimes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { addons } = await client.listAddons();
|
const { addons } = await clientWrapper.listAddons();
|
||||||
const extensions = addons.filter(a => a.debuggable);
|
const extensions = addons.filter(a => a.debuggable);
|
||||||
if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
|
if (runtime.type !== RUNTIMES.THIS_FIREFOX) {
|
||||||
// manifestURL can only be used when debugging local addons, remove this
|
// manifestURL can only be used when debugging local addons, remove this
|
||||||
@ -174,10 +173,14 @@ function requestWorkers() {
|
|||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
dispatch({ type: REQUEST_WORKERS_START });
|
dispatch({ type: REQUEST_WORKERS_START });
|
||||||
|
|
||||||
const client = getCurrentClient(getState().runtimes);
|
const clientWrapper = getCurrentClient(getState().runtimes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { otherWorkers, serviceWorkers, sharedWorkers } = await client.listWorkers();
|
const {
|
||||||
|
otherWorkers,
|
||||||
|
serviceWorkers,
|
||||||
|
sharedWorkers,
|
||||||
|
} = await clientWrapper.listWorkers();
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: REQUEST_WORKERS_SUCCESS,
|
type: REQUEST_WORKERS_SUCCESS,
|
||||||
@ -193,10 +196,10 @@ function requestWorkers() {
|
|||||||
|
|
||||||
function startServiceWorker(actor) {
|
function startServiceWorker(actor) {
|
||||||
return async (_, getState) => {
|
return async (_, getState) => {
|
||||||
const client = getCurrentClient(getState().runtimes);
|
const clientWrapper = getCurrentClient(getState().runtimes);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await client.request({ to: actor, type: "start" });
|
await clientWrapper.request({ to: actor, type: "start" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,10 @@ const {
|
|||||||
WATCH_RUNTIME_SUCCESS,
|
WATCH_RUNTIME_SUCCESS,
|
||||||
} = require("../constants");
|
} = require("../constants");
|
||||||
|
|
||||||
async function getRuntimeInfo(runtime, client) {
|
async function getRuntimeInfo(runtime, clientWrapper) {
|
||||||
const { type } = runtime;
|
const { type } = runtime;
|
||||||
const { brandName: name, channel, deviceName, version } =
|
const { brandName: name, channel, deviceName, version } =
|
||||||
await client.getDeviceDescription();
|
await clientWrapper.getDeviceDescription();
|
||||||
const icon =
|
const icon =
|
||||||
(channel === "release" || channel === "beta" || channel === "aurora")
|
(channel === "release" || channel === "beta" || channel === "aurora")
|
||||||
? `chrome://devtools/skin/images/aboutdebugging-firefox-${ channel }.svg`
|
? `chrome://devtools/skin/images/aboutdebugging-firefox-${ channel }.svg`
|
||||||
@ -68,17 +68,22 @@ function connectRuntime(id) {
|
|||||||
dispatch({ type: CONNECT_RUNTIME_START });
|
dispatch({ type: CONNECT_RUNTIME_START });
|
||||||
try {
|
try {
|
||||||
const runtime = findRuntimeById(id, getState().runtimes);
|
const runtime = findRuntimeById(id, getState().runtimes);
|
||||||
const { client, transportDetails } = await createClientForRuntime(runtime);
|
const { clientWrapper, transportDetails } = await createClientForRuntime(runtime);
|
||||||
const info = await getRuntimeInfo(runtime, client);
|
const info = await getRuntimeInfo(runtime, clientWrapper);
|
||||||
|
|
||||||
const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
|
const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
|
||||||
const connectionPromptEnabled = await client.getPreference(promptPrefName);
|
const connectionPromptEnabled = await clientWrapper.getPreference(promptPrefName);
|
||||||
const runtimeDetails = { connectionPromptEnabled, client, info, transportDetails };
|
const runtimeDetails = {
|
||||||
|
clientWrapper,
|
||||||
|
connectionPromptEnabled,
|
||||||
|
info,
|
||||||
|
transportDetails,
|
||||||
|
};
|
||||||
|
|
||||||
if (runtime.type === RUNTIMES.USB) {
|
if (runtime.type === RUNTIMES.USB) {
|
||||||
// `closed` event will be emitted when disabling remote debugging
|
// `closed` event will be emitted when disabling remote debugging
|
||||||
// on the connected USB runtime.
|
// on the connected USB runtime.
|
||||||
client.addOneTimeListener("closed", onUSBDebuggerClientClosed);
|
clientWrapper.addOneTimeListener("closed", onUSBDebuggerClientClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
@ -100,13 +105,13 @@ function disconnectRuntime(id) {
|
|||||||
dispatch({ type: DISCONNECT_RUNTIME_START });
|
dispatch({ type: DISCONNECT_RUNTIME_START });
|
||||||
try {
|
try {
|
||||||
const runtime = findRuntimeById(id, getState().runtimes);
|
const runtime = findRuntimeById(id, getState().runtimes);
|
||||||
const client = runtime.runtimeDetails.client;
|
const { clientWrapper } = runtime.runtimeDetails;
|
||||||
|
|
||||||
if (runtime.type === RUNTIMES.USB) {
|
if (runtime.type === RUNTIMES.USB) {
|
||||||
client.removeListener("closed", onUSBDebuggerClientClosed);
|
clientWrapper.removeListener("closed", onUSBDebuggerClientClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.close();
|
await clientWrapper.close();
|
||||||
|
|
||||||
if (runtime.type === RUNTIMES.THIS_FIREFOX) {
|
if (runtime.type === RUNTIMES.THIS_FIREFOX) {
|
||||||
DebuggerServer.destroy();
|
DebuggerServer.destroy();
|
||||||
@ -130,11 +135,11 @@ function updateConnectionPromptSetting(connectionPromptEnabled) {
|
|||||||
dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_START });
|
dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_START });
|
||||||
try {
|
try {
|
||||||
const runtime = getCurrentRuntime(getState().runtimes);
|
const runtime = getCurrentRuntime(getState().runtimes);
|
||||||
const client = runtime.runtimeDetails.client;
|
const { clientWrapper } = runtime.runtimeDetails;
|
||||||
const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
|
const promptPrefName = RUNTIME_PREFERENCE.CONNECTION_PROMPT;
|
||||||
await client.setPreference(promptPrefName, connectionPromptEnabled);
|
await clientWrapper.setPreference(promptPrefName, connectionPromptEnabled);
|
||||||
// Re-get actual value from the runtime.
|
// Re-get actual value from the runtime.
|
||||||
connectionPromptEnabled = await client.getPreference(promptPrefName);
|
connectionPromptEnabled = await clientWrapper.getPreference(promptPrefName);
|
||||||
|
|
||||||
dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
|
dispatch({ type: UPDATE_CONNECTION_PROMPT_SETTING_SUCCESS,
|
||||||
runtime, connectionPromptEnabled });
|
runtime, connectionPromptEnabled });
|
||||||
|
@ -13,7 +13,7 @@ const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
|||||||
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
|
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
|
||||||
|
|
||||||
const { PAGES } = require("../constants");
|
const { PAGES } = require("../constants");
|
||||||
const Types = require("../types");
|
const Types = require("../types/index");
|
||||||
|
|
||||||
const ConnectPage = createFactory(require("./connect/ConnectPage"));
|
const ConnectPage = createFactory(require("./connect/ConnectPage"));
|
||||||
const RuntimePage = createFactory(require("./RuntimePage"));
|
const RuntimePage = createFactory(require("./RuntimePage"));
|
||||||
|
@ -8,6 +8,8 @@ const { PureComponent } = require("devtools/client/shared/vendor/react");
|
|||||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||||
|
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays debug target.
|
* This component displays debug target.
|
||||||
*/
|
*/
|
||||||
@ -17,7 +19,7 @@ class DebugTargetItem extends PureComponent {
|
|||||||
actionComponent: PropTypes.any.isRequired,
|
actionComponent: PropTypes.any.isRequired,
|
||||||
detailComponent: PropTypes.any.isRequired,
|
detailComponent: PropTypes.any.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
target: PropTypes.object.isRequired,
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@ const Localized = createFactory(FluentReact.Localized);
|
|||||||
|
|
||||||
const DebugTargetItem = createFactory(require("./DebugTargetItem"));
|
const DebugTargetItem = createFactory(require("./DebugTargetItem"));
|
||||||
|
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays list of debug target.
|
* This component displays list of debug target.
|
||||||
*/
|
*/
|
||||||
@ -24,7 +26,7 @@ class DebugTargetList extends PureComponent {
|
|||||||
detailComponent: PropTypes.any.isRequired,
|
detailComponent: PropTypes.any.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
isCollapsed: PropTypes.bool.isRequired,
|
isCollapsed: PropTypes.bool.isRequired,
|
||||||
targets: PropTypes.arrayOf(PropTypes.object).isRequired,
|
targets: PropTypes.arrayOf(Types.debugTarget).isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
|||||||
const DebugTargetList = createFactory(require("./DebugTargetList"));
|
const DebugTargetList = createFactory(require("./DebugTargetList"));
|
||||||
|
|
||||||
const Actions = require("../../actions/index");
|
const Actions = require("../../actions/index");
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component provides list for debug target and name area.
|
* This component provides list for debug target and name area.
|
||||||
@ -24,7 +25,7 @@ class DebugTargetPane extends PureComponent {
|
|||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
isCollapsed: PropTypes.bool.isRequired,
|
isCollapsed: PropTypes.bool.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
targets: PropTypes.arrayOf(PropTypes.object).isRequired,
|
targets: PropTypes.arrayOf(Types.debugTarget).isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ const Localized = createFactory(FluentReact.Localized);
|
|||||||
|
|
||||||
const FieldPair = createFactory(require("./FieldPair"));
|
const FieldPair = createFactory(require("./FieldPair"));
|
||||||
|
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays detail information for extension.
|
* This component displays detail information for extension.
|
||||||
*/
|
*/
|
||||||
@ -21,7 +23,7 @@ class ExtensionDetail extends PureComponent {
|
|||||||
return {
|
return {
|
||||||
// Provided by wrapping the component with FluentReact.withLocalization.
|
// Provided by wrapping the component with FluentReact.withLocalization.
|
||||||
getString: PropTypes.func.isRequired,
|
getString: PropTypes.func.isRequired,
|
||||||
target: PropTypes.object.isRequired,
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
|||||||
const Localized = createFactory(FluentReact.Localized);
|
const Localized = createFactory(FluentReact.Localized);
|
||||||
|
|
||||||
const Actions = require("../../actions/index");
|
const Actions = require("../../actions/index");
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component provides inspect button.
|
* This component provides inspect button.
|
||||||
@ -20,13 +21,13 @@ class InspectAction extends PureComponent {
|
|||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
target: PropTypes.object.isRequired,
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
inspect() {
|
inspect() {
|
||||||
const { dispatch, target } = this.props;
|
const { dispatch, target } = this.props;
|
||||||
dispatch(Actions.inspectDebugTarget(target));
|
dispatch(Actions.inspectDebugTarget(target.type, target.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -13,6 +13,7 @@ const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
|||||||
const InspectAction = createFactory(require("./InspectAction"));
|
const InspectAction = createFactory(require("./InspectAction"));
|
||||||
|
|
||||||
const Actions = require("../../actions/index");
|
const Actions = require("../../actions/index");
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays buttons for service worker.
|
* This component displays buttons for service worker.
|
||||||
@ -23,7 +24,7 @@ class ServiceWorkerAction extends PureComponent {
|
|||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
// Provided by wrapping the component with FluentReact.withLocalization.
|
// Provided by wrapping the component with FluentReact.withLocalization.
|
||||||
getString: PropTypes.func.isRequired,
|
getString: PropTypes.func.isRequired,
|
||||||
target: PropTypes.object.isRequired,
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
const { PureComponent } = require("devtools/client/shared/vendor/react");
|
const { PureComponent } = require("devtools/client/shared/vendor/react");
|
||||||
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
const dom = require("devtools/client/shared/vendor/react-dom-factories");
|
||||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays detail information for tab.
|
* This component displays detail information for tab.
|
||||||
@ -14,7 +14,7 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
|||||||
class TabDetail extends PureComponent {
|
class TabDetail extends PureComponent {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
target: PropTypes.object.isRequired,
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ const Localized = createFactory(FluentReact.Localized);
|
|||||||
const InspectAction = createFactory(require("./InspectAction"));
|
const InspectAction = createFactory(require("./InspectAction"));
|
||||||
|
|
||||||
const Actions = require("../../actions/index");
|
const Actions = require("../../actions/index");
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component provides components that inspect/reload/remove temporary extension.
|
* This component provides components that inspect/reload/remove temporary extension.
|
||||||
@ -22,7 +23,7 @@ class TemporaryExtensionAction extends PureComponent {
|
|||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
target: PropTypes.object.isRequired,
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,15 +17,17 @@ const {
|
|||||||
|
|
||||||
const FieldPair = createFactory(require("./FieldPair"));
|
const FieldPair = createFactory(require("./FieldPair"));
|
||||||
|
|
||||||
|
const Types = require("../../types/index");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component displays detail information for worker.
|
* This component displays detail information for worker.
|
||||||
*/
|
*/
|
||||||
class WorkerDetail extends PureComponent {
|
class WorkerDetail extends PureComponent {
|
||||||
static get propTypes() {
|
static get propTypes() {
|
||||||
return {
|
return {
|
||||||
target: PropTypes.object.isRequired,
|
|
||||||
// Provided by wrapping the component with FluentReact.withLocalization.
|
// Provided by wrapping the component with FluentReact.withLocalization.
|
||||||
getString: PropTypes.func.isRequired,
|
getString: PropTypes.func.isRequired,
|
||||||
|
target: Types.debugTarget.isRequired,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
|||||||
const Localized = createFactory(FluentReact.Localized);
|
const Localized = createFactory(FluentReact.Localized);
|
||||||
|
|
||||||
const { PAGES, RUNTIMES } = require("../../constants");
|
const { PAGES, RUNTIMES } = require("../../constants");
|
||||||
const Types = require("../../types");
|
const Types = require("../../types/index");
|
||||||
loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
|
loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
|
||||||
|
|
||||||
const SidebarItem = createFactory(require("./SidebarItem"));
|
const SidebarItem = createFactory(require("./SidebarItem"));
|
||||||
|
@ -29,43 +29,45 @@ function debugTargetListenerMiddleware(store) {
|
|||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case WATCH_RUNTIME_SUCCESS: {
|
case WATCH_RUNTIME_SUCCESS: {
|
||||||
const { runtime } = action;
|
const { runtime } = action;
|
||||||
const { client } = runtime.runtimeDetails;
|
const { clientWrapper } = runtime.runtimeDetails;
|
||||||
|
|
||||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.TAB)) {
|
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.TAB)) {
|
||||||
client.addListener("tabListChanged", onTabsUpdated);
|
clientWrapper.addListener("tabListChanged", onTabsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.EXTENSION)) {
|
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.EXTENSION)) {
|
||||||
client.addListener("addonListChanged", onExtensionsUpdated);
|
clientWrapper.addListener("addonListChanged", onExtensionsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.WORKER)) {
|
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.WORKER)) {
|
||||||
client.addListener("workerListChanged", onWorkersUpdated);
|
clientWrapper.addListener("workerListChanged", onWorkersUpdated);
|
||||||
client.addListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
|
clientWrapper.addListener("serviceWorkerRegistrationListChanged",
|
||||||
client.addListener("processListChanged", onWorkersUpdated);
|
onWorkersUpdated);
|
||||||
client.addListener("registration-changed", onWorkersUpdated);
|
clientWrapper.addListener("processListChanged", onWorkersUpdated);
|
||||||
client.addListener("push-subscription-modified", onWorkersUpdated);
|
clientWrapper.addListener("registration-changed", onWorkersUpdated);
|
||||||
|
clientWrapper.addListener("push-subscription-modified", onWorkersUpdated);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case UNWATCH_RUNTIME_START: {
|
case UNWATCH_RUNTIME_START: {
|
||||||
const { runtime } = action;
|
const { runtime } = action;
|
||||||
const { client } = runtime.runtimeDetails;
|
const { clientWrapper } = runtime.runtimeDetails;
|
||||||
|
|
||||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.TAB)) {
|
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.TAB)) {
|
||||||
client.removeListener("tabListChanged", onTabsUpdated);
|
clientWrapper.removeListener("tabListChanged", onTabsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.EXTENSION)) {
|
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.EXTENSION)) {
|
||||||
client.removeListener("addonListChanged", onExtensionsUpdated);
|
clientWrapper.removeListener("addonListChanged", onExtensionsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.WORKER)) {
|
if (isSupportedDebugTarget(runtime.type, DEBUG_TARGETS.WORKER)) {
|
||||||
client.removeListener("workerListChanged", onWorkersUpdated);
|
clientWrapper.removeListener("workerListChanged", onWorkersUpdated);
|
||||||
client.removeListener("serviceWorkerRegistrationListChanged", onWorkersUpdated);
|
clientWrapper.removeListener("serviceWorkerRegistrationListChanged",
|
||||||
client.removeListener("processListChanged", onWorkersUpdated);
|
onWorkersUpdated);
|
||||||
client.removeListener("registration-changed", onWorkersUpdated);
|
clientWrapper.removeListener("processListChanged", onWorkersUpdated);
|
||||||
client.removeListener("push-subscription-modified", onWorkersUpdated);
|
clientWrapper.removeListener("registration-changed", onWorkersUpdated);
|
||||||
|
clientWrapper.removeListener("push-subscription-modified", onWorkersUpdated);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,14 @@ function toComponentData(workers, isServiceWorker) {
|
|||||||
return workers.map(worker => {
|
return workers.map(worker => {
|
||||||
// Here `worker` is the worker object created by RootFront.listAllWorkers
|
// Here `worker` is the worker object created by RootFront.listAllWorkers
|
||||||
const type = DEBUG_TARGETS.WORKER;
|
const type = DEBUG_TARGETS.WORKER;
|
||||||
const front = worker.workerTargetFront;
|
|
||||||
const icon = "chrome://devtools/skin/images/debugging-workers.svg";
|
const icon = "chrome://devtools/skin/images/debugging-workers.svg";
|
||||||
let { fetch, name, registrationActor, scope } = worker;
|
let { fetch, name, registrationActor, scope, workerTargetFront } = worker;
|
||||||
|
|
||||||
|
// For registering service workers, workerTargetFront will not be available.
|
||||||
|
// The only valid identifier we can use at that point is the actorID for the
|
||||||
|
// service worker registration.
|
||||||
|
const id = workerTargetFront ? workerTargetFront.actorID : registrationActor;
|
||||||
|
|
||||||
let isActive = false;
|
let isActive = false;
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
let status = null;
|
let status = null;
|
||||||
@ -60,10 +65,6 @@ function toComponentData(workers, isServiceWorker) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
|
||||||
icon,
|
|
||||||
front,
|
|
||||||
type,
|
|
||||||
details: {
|
details: {
|
||||||
fetch,
|
fetch,
|
||||||
isActive,
|
isActive,
|
||||||
@ -72,6 +73,10 @@ function toComponentData(workers, isServiceWorker) {
|
|||||||
scope,
|
scope,
|
||||||
status,
|
status,
|
||||||
},
|
},
|
||||||
|
icon,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
type,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ async function createLocalClient() {
|
|||||||
DebuggerServer.registerAllActors();
|
DebuggerServer.registerAllActors();
|
||||||
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
const client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||||
await client.connect();
|
await client.connect();
|
||||||
return { client: new ClientWrapper(client) };
|
return { clientWrapper: new ClientWrapper(client) };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNetworkClient(host, port) {
|
async function createNetworkClient(host, port) {
|
||||||
@ -24,7 +24,7 @@ async function createNetworkClient(host, port) {
|
|||||||
const transport = await DebuggerClient.socketConnect(transportDetails);
|
const transport = await DebuggerClient.socketConnect(transportDetails);
|
||||||
const client = new DebuggerClient(transport);
|
const client = new DebuggerClient(transport);
|
||||||
await client.connect();
|
await client.connect();
|
||||||
return { client: new ClientWrapper(client), transportDetails };
|
return { clientWrapper: new ClientWrapper(client), transportDetails };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUSBClient(socketPath) {
|
async function createUSBClient(socketPath) {
|
||||||
|
@ -12,7 +12,7 @@ exports.getCurrentRuntime = getCurrentRuntime;
|
|||||||
|
|
||||||
function getCurrentClient(runtimesState) {
|
function getCurrentClient(runtimesState) {
|
||||||
const runtimeDetails = getCurrentRuntimeDetails(runtimesState);
|
const runtimeDetails = getCurrentRuntimeDetails(runtimesState);
|
||||||
return runtimeDetails ? runtimeDetails.client : null;
|
return runtimeDetails ? runtimeDetails.clientWrapper : null;
|
||||||
}
|
}
|
||||||
exports.getCurrentClient = getCurrentClient;
|
exports.getCurrentClient = getCurrentClient;
|
||||||
|
|
||||||
|
@ -8,11 +8,11 @@ DIRS += [
|
|||||||
'middleware',
|
'middleware',
|
||||||
'modules',
|
'modules',
|
||||||
'reducers',
|
'reducers',
|
||||||
|
'types',
|
||||||
]
|
]
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
'base.css',
|
'base.css',
|
||||||
'constants.js',
|
'constants.js',
|
||||||
'create-store.js',
|
'create-store.js',
|
||||||
'types.js',
|
|
||||||
)
|
)
|
||||||
|
60
devtools/client/aboutdebugging-new/src/types/debug-target.js
Normal file
60
devtools/client/aboutdebugging-new/src/types/debug-target.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||||
|
|
||||||
|
const extensionTargetDetails = {
|
||||||
|
// actor ID for this extention.
|
||||||
|
actor: PropTypes.string.isRequired,
|
||||||
|
location: PropTypes.string.isRequired,
|
||||||
|
// manifestURL points to the manifest.json file. This URL is only valid when debugging
|
||||||
|
// local extensions so it might be null.
|
||||||
|
manifestURL: PropTypes.string,
|
||||||
|
// unique extension id.
|
||||||
|
uuid: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabTargetDetails = {
|
||||||
|
// the url of the tab.
|
||||||
|
url: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const workerTargetDetails = {
|
||||||
|
// (service worker specific) one of "LISTENING", "NOT_LISTENING". undefined otherwise.
|
||||||
|
fetch: PropTypes.string,
|
||||||
|
// (service worker specific) true if they reached the activated state.
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
// (service worker specific) true if they are currently running.
|
||||||
|
isRunning: PropTypes.bool,
|
||||||
|
// actor id for the ServiceWorkerRegistration related to this service worker.
|
||||||
|
registrationActor: PropTypes.string,
|
||||||
|
// (service worker specific) scope of the service worker registration.
|
||||||
|
scope: PropTypes.string,
|
||||||
|
// (service worker specific) one of "RUNNING", "REGISTERING", "STOPPED".
|
||||||
|
status: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const debugTarget = {
|
||||||
|
// details property will contain a type-specific object.
|
||||||
|
details: PropTypes.oneOfType([
|
||||||
|
PropTypes.shape(extensionTargetDetails),
|
||||||
|
PropTypes.shape(tabTargetDetails),
|
||||||
|
PropTypes.shape(workerTargetDetails),
|
||||||
|
]).isRequired,
|
||||||
|
// icon to display for the debug target.
|
||||||
|
icon: PropTypes.string.isRequired,
|
||||||
|
// unique id for the target (unique in the scope of the application lifecycle).
|
||||||
|
// - extensions: {String} extension id (for instance "someextension@mozilla.org")
|
||||||
|
// - tabs: {Number} outerWindowID
|
||||||
|
// - workers: {String} id for the WorkerTargetActor corresponding to the worker
|
||||||
|
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
||||||
|
// display name for the debug target.
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
// one of "EXTENSION", "TAB", "WORKER".
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.debugTarget = PropTypes.shape(debugTarget);
|
13
devtools/client/aboutdebugging-new/src/types/index.js
Normal file
13
devtools/client/aboutdebugging-new/src/types/index.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { debugTarget } = require("./debug-target");
|
||||||
|
const { runtime } = require("./runtime");
|
||||||
|
|
||||||
|
module.exports = Object.assign({}, {
|
||||||
|
debugTarget,
|
||||||
|
runtime,
|
||||||
|
});
|
9
devtools/client/aboutdebugging-new/src/types/moz.build
Normal file
9
devtools/client/aboutdebugging-new/src/types/moz.build
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
DevToolsModules(
|
||||||
|
'debug-target.js',
|
||||||
|
'index.js',
|
||||||
|
'runtime.js',
|
||||||
|
)
|
@ -5,7 +5,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||||
const { ClientWrapper } = require("./modules/client-wrapper");
|
const { ClientWrapper } = require("../modules/client-wrapper");
|
||||||
|
|
||||||
const runtimeInfo = {
|
const runtimeInfo = {
|
||||||
// device name which is running the runtime,
|
// device name which is running the runtime,
|
||||||
@ -32,7 +32,7 @@ const runtimeTransportDetails = {
|
|||||||
|
|
||||||
const runtimeDetails = {
|
const runtimeDetails = {
|
||||||
// ClientWrapper built using a DebuggerClient for the runtime
|
// ClientWrapper built using a DebuggerClient for the runtime
|
||||||
client: PropTypes.instanceOf(ClientWrapper).isRequired,
|
clientWrapper: PropTypes.instanceOf(ClientWrapper).isRequired,
|
||||||
|
|
||||||
// reflect devtools.debugger.prompt-connection preference of this runtime
|
// reflect devtools.debugger.prompt-connection preference of this runtime
|
||||||
connectionPromptEnabled: PropTypes.bool.isRequired,
|
connectionPromptEnabled: PropTypes.bool.isRequired,
|
@ -35,7 +35,7 @@ class UsbMocks {
|
|||||||
this.runtimeClientFactoryMock = createRuntimeClientFactoryMock();
|
this.runtimeClientFactoryMock = createRuntimeClientFactoryMock();
|
||||||
this._clients = {};
|
this._clients = {};
|
||||||
this.runtimeClientFactoryMock.createClientForRuntime = runtime => {
|
this.runtimeClientFactoryMock.createClientForRuntime = runtime => {
|
||||||
return { client: this._clients[runtime.id] };
|
return { clientWrapper: this._clients[runtime.id] };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a client for THIS_FIREFOX, since about:debugging will start on the This Firefox
|
// Add a client for THIS_FIREFOX, since about:debugging will start on the This Firefox
|
||||||
|
@ -266,7 +266,9 @@ const reducers = {
|
|||||||
return addDecl.index === decl.index;
|
return addDecl.index === decl.index;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (rule.remove[removeIndex] && rule.remove[removeIndex].value === decl.value) {
|
if (rule.remove[removeIndex] &&
|
||||||
|
rule.remove[removeIndex].value === decl.value &&
|
||||||
|
rule.remove[removeIndex].property === decl.property) {
|
||||||
// Delete any previous remove operation which would be canceled out by this add.
|
// Delete any previous remove operation which would be canceled out by this add.
|
||||||
rule.remove.splice(removeIndex, 1);
|
rule.remove.splice(removeIndex, 1);
|
||||||
} else if (rule.add[addIndex]) {
|
} else if (rule.add[addIndex]) {
|
||||||
|
@ -17,4 +17,5 @@ support-files =
|
|||||||
[browser_changes_declaration_edit_value.js]
|
[browser_changes_declaration_edit_value.js]
|
||||||
[browser_changes_declaration_remove_ahead.js]
|
[browser_changes_declaration_remove_ahead.js]
|
||||||
[browser_changes_declaration_remove.js]
|
[browser_changes_declaration_remove.js]
|
||||||
|
[browser_changes_declaration_rename.js]
|
||||||
[browser_changes_rule_selector.js]
|
[browser_changes_rule_selector.js]
|
||||||
|
@ -19,7 +19,6 @@ add_task(async function() {
|
|||||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||||
const { inspector, view: ruleView } = await openRuleView();
|
const { inspector, view: ruleView } = await openRuleView();
|
||||||
const { document: doc, store } = selectChangesView(inspector);
|
const { document: doc, store } = selectChangesView(inspector);
|
||||||
const panel = doc.querySelector("#sidebar-panel-changes");
|
|
||||||
|
|
||||||
await selectNode("div", inspector);
|
await selectNode("div", inspector);
|
||||||
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
@ -31,7 +30,7 @@ add_task(async function() {
|
|||||||
info("Wait for change to be tracked");
|
info("Wait for change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
let removedDeclarations = panel.querySelectorAll(".diff-remove");
|
let removedDeclarations = getRemovedDeclarations(doc);
|
||||||
is(removedDeclarations.length, 1, "Only one declaration was tracked as removed");
|
is(removedDeclarations.length, 1, "Only one declaration was tracked as removed");
|
||||||
|
|
||||||
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
|
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
|
||||||
@ -40,8 +39,8 @@ add_task(async function() {
|
|||||||
info("Wait for change to be tracked");
|
info("Wait for change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
const addedDeclarations = panel.querySelectorAll(".diff-add");
|
const addedDeclarations = getAddedDeclarations(doc);
|
||||||
removedDeclarations = panel.querySelectorAll(".diff-remove");
|
removedDeclarations = getRemovedDeclarations(doc);
|
||||||
is(addedDeclarations.length, 0, "No declarations were tracked as added");
|
is(addedDeclarations.length, 0, "No declarations were tracked as added");
|
||||||
is(removedDeclarations.length, 0, "No declarations were tracked as removed");
|
is(removedDeclarations.length, 0, "No declarations were tracked as removed");
|
||||||
});
|
});
|
||||||
|
@ -18,15 +18,14 @@ add_task(async function() {
|
|||||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||||
const { inspector, view: ruleView } = await openRuleView();
|
const { inspector, view: ruleView } = await openRuleView();
|
||||||
const { document: doc, store } = selectChangesView(inspector);
|
const { document: doc, store } = selectChangesView(inspector);
|
||||||
const panel = doc.querySelector("#sidebar-panel-changes");
|
|
||||||
|
|
||||||
await selectNode("div", inspector);
|
await selectNode("div", inspector);
|
||||||
await testAddDuplicateDeclarations(ruleView, store, panel);
|
await testAddDuplicateDeclarations(ruleView, store, doc);
|
||||||
await testChangeDuplicateDeclarations(ruleView, store, panel);
|
await testChangeDuplicateDeclarations(ruleView, store, doc);
|
||||||
await testRemoveDuplicateDeclarations(ruleView, store, panel);
|
await testRemoveDuplicateDeclarations(ruleView, store, doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function testAddDuplicateDeclarations(ruleView, store, panel) {
|
async function testAddDuplicateDeclarations(ruleView, store, doc) {
|
||||||
info(`Test that adding declarations with the same property name and value
|
info(`Test that adding declarations with the same property name and value
|
||||||
are both tracked.`);
|
are both tracked.`);
|
||||||
|
|
||||||
@ -42,18 +41,17 @@ async function testAddDuplicateDeclarations(ruleView, store, panel) {
|
|||||||
info("Wait for the change to be tracked");
|
info("Wait for the change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
const addDecl = panel.querySelectorAll(".declaration.diff-add");
|
const addDecl = getAddedDeclarations(doc);
|
||||||
is(addDecl.length, 2, "Two declarations were tracked as added");
|
is(addDecl.length, 2, "Two declarations were tracked as added");
|
||||||
is(addDecl.item(0).querySelector(".declaration-value").textContent, "red",
|
is(addDecl[0].value, "red",
|
||||||
"First declaration has correct property value"
|
"First declaration has correct property value"
|
||||||
);
|
);
|
||||||
is(addDecl.item(0).querySelector(".declaration-value").textContent,
|
is(addDecl[0].value, addDecl[1].value,
|
||||||
addDecl.item(1).querySelector(".declaration-value").textContent,
|
|
||||||
"First and second declarations have identical property values"
|
"First and second declarations have identical property values"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testChangeDuplicateDeclarations(ruleView, store, panel) {
|
async function testChangeDuplicateDeclarations(ruleView, store, doc) {
|
||||||
info("Test that changing one of the duplicate declarations won't change the other");
|
info("Test that changing one of the duplicate declarations won't change the other");
|
||||||
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
const prop = rule.textProps[0];
|
const prop = rule.textProps[0];
|
||||||
@ -64,16 +62,12 @@ async function testChangeDuplicateDeclarations(ruleView, store, panel) {
|
|||||||
info("Wait for the change to be tracked");
|
info("Wait for the change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
const addDecl = panel.querySelectorAll(".declaration.diff-add");
|
const addDecl = getAddedDeclarations(doc);
|
||||||
is(addDecl.item(0).querySelector(".declaration-value").textContent, "black",
|
is(addDecl[0].value, "black", "First declaration has changed property value");
|
||||||
"First declaration has changed property value"
|
is(addDecl[1].value, "red", "Second declaration has not changed property value");
|
||||||
);
|
|
||||||
is(addDecl.item(1).querySelector(".declaration-value").textContent, "red",
|
|
||||||
"Second declaration has not changed property value"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testRemoveDuplicateDeclarations(ruleView, store, panel) {
|
async function testRemoveDuplicateDeclarations(ruleView, store, doc) {
|
||||||
info(`Test that removing the first of the duplicate declarations
|
info(`Test that removing the first of the duplicate declarations
|
||||||
will not remove the second.`);
|
will not remove the second.`);
|
||||||
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
@ -85,12 +79,12 @@ async function testRemoveDuplicateDeclarations(ruleView, store, panel) {
|
|||||||
info("Wait for the change to be tracked");
|
info("Wait for the change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
const addDecl = panel.querySelectorAll(".declaration.diff-add");
|
const addDecl = getAddedDeclarations(doc);
|
||||||
const removeDecl = panel.querySelectorAll(".declaration.diff-remove");
|
const removeDecl = getRemovedDeclarations(doc);
|
||||||
// Expect no remove operation tracked because it cancels out the original add operation.
|
// Expect no remove operation tracked because it cancels out the original add operation.
|
||||||
is(removeDecl.length, 0, "No declaration was tracked as removed");
|
is(removeDecl.length, 0, "No declaration was tracked as removed");
|
||||||
is(addDecl.length, 1, "Just one declaration left tracked as added");
|
is(addDecl.length, 1, "Just one declaration left tracked as added");
|
||||||
is(addDecl.item(0).querySelector(".declaration-value").textContent, "red",
|
is(addDecl[0].value, "red",
|
||||||
"Leftover declaration has property value of the former second declaration"
|
"Leftover declaration has property value of the former second declaration"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -53,15 +53,14 @@ add_task(async function() {
|
|||||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||||
const { inspector, view: ruleView } = await openRuleView();
|
const { inspector, view: ruleView } = await openRuleView();
|
||||||
const { document: doc, store } = selectChangesView(inspector);
|
const { document: doc, store } = selectChangesView(inspector);
|
||||||
const panel = doc.querySelector("#sidebar-panel-changes");
|
|
||||||
|
|
||||||
await selectNode("div", inspector);
|
await selectNode("div", inspector);
|
||||||
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
const prop = rule.textProps[0];
|
const prop = rule.textProps[0];
|
||||||
|
|
||||||
let onTrackChange;
|
let onTrackChange;
|
||||||
let removeValue;
|
let removeDecl;
|
||||||
let addValue;
|
let addDecl;
|
||||||
|
|
||||||
for (const { value, add, remove } of VALUE_CHANGE_ITERATIONS) {
|
for (const { value, add, remove } of VALUE_CHANGE_ITERATIONS) {
|
||||||
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
|
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
|
||||||
@ -71,25 +70,24 @@ add_task(async function() {
|
|||||||
info("Wait for the change to be tracked");
|
info("Wait for the change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
addValue = panel.querySelector(".diff-add .declaration-value");
|
addDecl = getAddedDeclarations(doc);
|
||||||
removeValue = panel.querySelector(".diff-remove .declaration-value");
|
removeDecl = getRemovedDeclarations(doc);
|
||||||
|
|
||||||
if (add) {
|
if (add) {
|
||||||
is(addValue.textContent, add.value,
|
is(addDecl[0].value, add.value,
|
||||||
`Added declaration has expected value: ${add.value}`);
|
`Added declaration has expected value: ${add.value}`);
|
||||||
is(panel.querySelectorAll(".diff-add").length, 1,
|
is(addDecl.length, 1, "Only one declaration was tracked as added.");
|
||||||
"Only one declaration was tracked as added.");
|
|
||||||
} else {
|
} else {
|
||||||
ok(!addValue, `Added declaration was cleared`);
|
is(addDecl.length, 0, "Added declaration was cleared");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remove) {
|
if (remove) {
|
||||||
is(removeValue.textContent, remove.value,
|
is(removeDecl[0].value, remove.value,
|
||||||
`Removed declaration has expected value: ${remove.value}`);
|
`Removed declaration has expected value: ${remove.value}`);
|
||||||
is(panel.querySelectorAll(".diff-remove").length, 1,
|
is(removeDecl.length, 1,
|
||||||
"Only one declaration was tracked as removed.");
|
"Only one declaration was tracked as removed.");
|
||||||
} else {
|
} else {
|
||||||
ok(!removeValue, `Removed declaration was cleared`);
|
is(removeDecl.length, 0, "Removed declaration was cleared");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,6 @@ add_task(async function() {
|
|||||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||||
const { inspector, view: ruleView } = await openRuleView();
|
const { inspector, view: ruleView } = await openRuleView();
|
||||||
const { document: doc, store } = selectChangesView(inspector);
|
const { document: doc, store } = selectChangesView(inspector);
|
||||||
const panel = doc.querySelector("#sidebar-panel-changes");
|
|
||||||
|
|
||||||
await selectNode("div", inspector);
|
await selectNode("div", inspector);
|
||||||
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
@ -31,8 +30,8 @@ add_task(async function() {
|
|||||||
info("Wait for change to be tracked");
|
info("Wait for change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
const removeName = panel.querySelector(".diff-remove .declaration-name");
|
const removeDecl = getRemovedDeclarations(doc);
|
||||||
const removeValue = panel.querySelector(".diff-remove .declaration-value");
|
is(removeDecl.length, 1, "One declaration was tracked as removed");
|
||||||
is(removeName.textContent, "color", "Correct declaration name was tracked as removed");
|
is(removeDecl[0].property, "color", "Correct declaration name was tracked as removed");
|
||||||
is(removeValue.textContent, "red", "Correct declaration value was tracked as removed");
|
is(removeDecl[0].value, "red", "Correct declaration value was tracked as removed");
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,6 @@ add_task(async function() {
|
|||||||
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||||
const { inspector, view: ruleView } = await openRuleView();
|
const { inspector, view: ruleView } = await openRuleView();
|
||||||
const { document: doc, store } = selectChangesView(inspector);
|
const { document: doc, store } = selectChangesView(inspector);
|
||||||
const panel = doc.querySelector("#sidebar-panel-changes");
|
|
||||||
|
|
||||||
await selectNode("div", inspector);
|
await selectNode("div", inspector);
|
||||||
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
@ -44,16 +43,12 @@ add_task(async function() {
|
|||||||
info("Wait for change to be tracked");
|
info("Wait for change to be tracked");
|
||||||
await onTrackChange;
|
await onTrackChange;
|
||||||
|
|
||||||
const removeDecl = panel.querySelectorAll(".declaration.diff-remove");
|
const removeDecl = getRemovedDeclarations(doc);
|
||||||
const addDecl = panel.querySelectorAll(".declaration.diff-add");
|
const addDecl = getAddedDeclarations(doc);
|
||||||
|
|
||||||
is(removeDecl.length, 2, "Two declarations tracked as removed");
|
is(removeDecl.length, 2, "Two declarations tracked as removed");
|
||||||
is(addDecl.length, 1, "One declaration tracked as added");
|
is(addDecl.length, 1, "One declaration tracked as added");
|
||||||
// Ensure changes to the second declaration were tracked after removing the first one.
|
// Ensure changes to the second declaration were tracked after removing the first one.
|
||||||
is(addDecl.item(0).querySelector(".declaration-name").textContent, "display",
|
is(addDecl[0].property, "display", "Added declaration has updated property name");
|
||||||
"Added declaration has updated property name"
|
is(addDecl[0].value, "flex", "Added declaration has updated property value");
|
||||||
);
|
|
||||||
is(addDecl.item(0).querySelector(".declaration-value").textContent, "flex",
|
|
||||||
"Added declaration has updated property value"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// Test that renaming the property of a CSS declaration in the Rule view is tracked.
|
||||||
|
|
||||||
|
const TEST_URI = `
|
||||||
|
<style type='text/css'>
|
||||||
|
div {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
add_task(async function() {
|
||||||
|
await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
|
||||||
|
const { inspector, view: ruleView } = await openRuleView();
|
||||||
|
const { document: doc, store } = selectChangesView(inspector);
|
||||||
|
|
||||||
|
await selectNode("div", inspector);
|
||||||
|
const rule = getRuleViewRuleEditor(ruleView, 1).rule;
|
||||||
|
const prop = rule.textProps[0];
|
||||||
|
|
||||||
|
let onTrackChange;
|
||||||
|
|
||||||
|
const oldPropertyName = "color";
|
||||||
|
const newPropertyName = "background-color";
|
||||||
|
|
||||||
|
info(`Rename the CSS declaration name to ${newPropertyName}`);
|
||||||
|
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
|
||||||
|
await renameProperty(ruleView, prop, newPropertyName);
|
||||||
|
info("Wait for the change to be tracked");
|
||||||
|
await onTrackChange;
|
||||||
|
|
||||||
|
let removeDecl = getRemovedDeclarations(doc);
|
||||||
|
let addDecl = getAddedDeclarations(doc);
|
||||||
|
|
||||||
|
is(removeDecl.length, 1, "One declaration tracked as removed");
|
||||||
|
is(removeDecl[0].property, oldPropertyName,
|
||||||
|
`Removed declaration has old property name: ${oldPropertyName}`
|
||||||
|
);
|
||||||
|
is(addDecl.length, 1, "One declaration tracked as added");
|
||||||
|
is(addDecl[0].property, newPropertyName,
|
||||||
|
`Added declaration has new property name: ${newPropertyName}`
|
||||||
|
);
|
||||||
|
|
||||||
|
info(`Reverting the CSS declaration name to ${oldPropertyName} should clear changes.`);
|
||||||
|
onTrackChange = waitUntilAction(store, "TRACK_CHANGE");
|
||||||
|
await renameProperty(ruleView, prop, oldPropertyName);
|
||||||
|
info("Wait for the change to be tracked");
|
||||||
|
await onTrackChange;
|
||||||
|
|
||||||
|
removeDecl = getRemovedDeclarations(doc);
|
||||||
|
addDecl = getAddedDeclarations(doc);
|
||||||
|
|
||||||
|
is(removeDecl.length, 0, "No declaration tracked as removed");
|
||||||
|
is(addDecl.length, 0, "No declaration tracked as added");
|
||||||
|
});
|
@ -29,3 +29,34 @@ registerCleanupFunction(() => {
|
|||||||
Services.prefs.clearUserPref("devtools.inspector.changes.enabled");
|
Services.prefs.clearUserPref("devtools.inspector.changes.enabled");
|
||||||
Services.prefs.clearUserPref("devtools.inspector.three-pane-enabled");
|
Services.prefs.clearUserPref("devtools.inspector.three-pane-enabled");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of objects with property/value pairs of the CSS declarations rendered
|
||||||
|
* in the Changes panel.
|
||||||
|
*
|
||||||
|
* @param {Document} panelDoc
|
||||||
|
* Host document of the Changes panel.
|
||||||
|
* @param {String} selector
|
||||||
|
* Optional selector to filter rendered declaration DOM elements.
|
||||||
|
* One of ".diff-remove" or ".diff-add".
|
||||||
|
* If omitted, all declarations will be returned.
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
function getDeclarations(panelDoc, selector = "") {
|
||||||
|
const els = panelDoc.querySelectorAll(`#sidebar-panel-changes .declaration${selector}`);
|
||||||
|
|
||||||
|
return [...els].map(el => {
|
||||||
|
return {
|
||||||
|
property: el.querySelector(".declaration-name").textContent,
|
||||||
|
value: el.querySelector(".declaration-value").textContent,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAddedDeclarations(panelDoc) {
|
||||||
|
return getDeclarations(panelDoc, ".diff-add");
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRemovedDeclarations(panelDoc) {
|
||||||
|
return getDeclarations(panelDoc, ".diff-remove");
|
||||||
|
}
|
||||||
|
@ -366,6 +366,34 @@ var setProperty = async function(view, textProp, value,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the name of a property in a rule in the rule-view.
|
||||||
|
*
|
||||||
|
* @param {CssRuleView} view
|
||||||
|
* The instance of the rule-view panel.
|
||||||
|
* @param {TextProperty} textProp
|
||||||
|
* The instance of the TextProperty to be changed.
|
||||||
|
* @param {String} name
|
||||||
|
* The new property name.
|
||||||
|
*/
|
||||||
|
var renameProperty = async function(view, textProp, name) {
|
||||||
|
await focusEditableField(view, textProp.editor.nameSpan);
|
||||||
|
|
||||||
|
const onNameDone = view.once("ruleview-changed");
|
||||||
|
info(`Rename the property to ${name}`);
|
||||||
|
EventUtils.sendString(name, view.styleWindow);
|
||||||
|
EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
|
||||||
|
info("Wait for property name.");
|
||||||
|
await onNameDone;
|
||||||
|
// Renaming the property auto-advances the focus to the value input. Exiting without
|
||||||
|
// committing will still fire a change event. @see TextPropertyEditor._onValueDone().
|
||||||
|
// Wait for that event too before proceeding.
|
||||||
|
const onValueDone = view.once("ruleview-changed");
|
||||||
|
EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
|
||||||
|
info("Wait for property value.");
|
||||||
|
await onValueDone;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulate removing a property from an existing rule in the rule-view.
|
* Simulate removing a property from an existing rule in the rule-view.
|
||||||
*
|
*
|
||||||
|
@ -98,7 +98,9 @@ public:
|
|||||||
// being connected.
|
// being connected.
|
||||||
void Unbind();
|
void Unbind();
|
||||||
|
|
||||||
// Only intended for UA widgets / special shadow roots.
|
// Only intended for UA widgets / special shadow roots, or for handling
|
||||||
|
// failure cases when adopting (see BlastSubtreeToPieces).
|
||||||
|
//
|
||||||
// Forgets our shadow host and unbinds all our kids.
|
// Forgets our shadow host and unbinds all our kids.
|
||||||
void Unattach();
|
void Unattach();
|
||||||
|
|
||||||
|
17
dom/base/crashtests/1508845.html
Normal file
17
dom/base/crashtests/1508845.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
window.onload=function(){
|
||||||
|
let bigShadowTree = `<div>`;
|
||||||
|
for (let i = 0; i < 10; ++i)
|
||||||
|
bigShadowTree = bigShadowTree + bigShadowTree;
|
||||||
|
b.attachShadow({ mode: 'open' }).innerHTML = bigShadowTree;
|
||||||
|
// Create wrappers for all those elements.
|
||||||
|
[...b.shadowRoot.querySelectorAll('*')].forEach(() => {});
|
||||||
|
document.documentElement.addEventListener('DOMNodeRemoved', function(){
|
||||||
|
window.frames[0].document.body.appendChild(b)
|
||||||
|
})
|
||||||
|
window.frames[0].document.body.appendChild(a)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<mark id='a'>
|
||||||
|
<iframe></iframe>
|
||||||
|
<div id='b'>
|
@ -245,3 +245,4 @@ load 1459688.html
|
|||||||
load 1460794.html
|
load 1460794.html
|
||||||
load 1505875.html
|
load 1505875.html
|
||||||
load 1505811.html
|
load 1505811.html
|
||||||
|
load 1508845.html
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
#include "mozilla/dom/HTMLInputElement.h"
|
#include "mozilla/dom/HTMLInputElement.h"
|
||||||
#include "mozilla/dom/HTMLSlotElement.h"
|
#include "mozilla/dom/HTMLSlotElement.h"
|
||||||
#include "mozilla/dom/HTMLTemplateElement.h"
|
#include "mozilla/dom/HTMLTemplateElement.h"
|
||||||
|
#include "mozilla/dom/HTMLTextAreaElement.h"
|
||||||
#include "mozilla/dom/IDTracker.h"
|
#include "mozilla/dom/IDTracker.h"
|
||||||
#include "mozilla/dom/MouseEventBinding.h"
|
#include "mozilla/dom/MouseEventBinding.h"
|
||||||
#include "mozilla/dom/KeyboardEventBinding.h"
|
#include "mozilla/dom/KeyboardEventBinding.h"
|
||||||
@ -85,6 +86,7 @@
|
|||||||
#include "mozilla/dom/Selection.h"
|
#include "mozilla/dom/Selection.h"
|
||||||
#include "mozilla/Services.h"
|
#include "mozilla/Services.h"
|
||||||
#include "mozilla/StaticPrefs.h"
|
#include "mozilla/StaticPrefs.h"
|
||||||
|
#include "mozilla/TextEditor.h"
|
||||||
#include "mozilla/TextEvents.h"
|
#include "mozilla/TextEvents.h"
|
||||||
#include "nsArrayUtils.h"
|
#include "nsArrayUtils.h"
|
||||||
#include "nsAString.h"
|
#include "nsAString.h"
|
||||||
@ -4367,6 +4369,8 @@ nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
|
|||||||
Composed aComposed,
|
Composed aComposed,
|
||||||
bool* aDefaultAction)
|
bool* aDefaultAction)
|
||||||
{
|
{
|
||||||
|
MOZ_ASSERT(!aEventName.EqualsLiteral("input"),
|
||||||
|
"Use DispatchInputEvent() instead");
|
||||||
return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
|
return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
|
||||||
aComposed, Trusted::eYes, aDefaultAction);
|
aComposed, Trusted::eYes, aDefaultAction);
|
||||||
}
|
}
|
||||||
@ -4450,6 +4454,110 @@ nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget,
|
|||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
nsresult
|
||||||
|
nsContentUtils::DispatchInputEvent(Element* aEventTargetElement)
|
||||||
|
{
|
||||||
|
RefPtr<TextEditor> textEditor; // See bug 1506439
|
||||||
|
return DispatchInputEvent(aEventTargetElement, textEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
nsresult
|
||||||
|
nsContentUtils::DispatchInputEvent(Element* aEventTargetElement,
|
||||||
|
TextEditor* aTextEditor)
|
||||||
|
{
|
||||||
|
if (NS_WARN_IF(!aEventTargetElement)) {
|
||||||
|
return NS_ERROR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is called from editor, the instance should be set to aTextEditor.
|
||||||
|
// Otherwise, we need to look for an editor for aEventTargetElement.
|
||||||
|
// However, we don't need to do it for HTMLEditor since nobody shouldn't
|
||||||
|
// dispatch "input" event for HTMLEditor except HTMLEditor itself.
|
||||||
|
bool useInputEvent = false;
|
||||||
|
if (aTextEditor) {
|
||||||
|
useInputEvent = true;
|
||||||
|
} else if (HTMLTextAreaElement* textAreaElement=
|
||||||
|
HTMLTextAreaElement::FromNode(aEventTargetElement)) {
|
||||||
|
aTextEditor = textAreaElement->GetTextEditorWithoutCreation();
|
||||||
|
useInputEvent = true;
|
||||||
|
} else if (HTMLInputElement* inputElement =
|
||||||
|
HTMLInputElement::FromNode(aEventTargetElement)) {
|
||||||
|
if (inputElement->IsInputEventTarget()) {
|
||||||
|
aTextEditor = inputElement->GetTextEditorWithoutCreation();
|
||||||
|
useInputEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
else {
|
||||||
|
nsCOMPtr<nsITextControlElement> textControlElement =
|
||||||
|
do_QueryInterface(aEventTargetElement);
|
||||||
|
MOZ_ASSERT(!textControlElement,
|
||||||
|
"The event target may have editor, but we've not known it yet.");
|
||||||
|
}
|
||||||
|
#endif // #ifdef DEBUG
|
||||||
|
|
||||||
|
if (!useInputEvent) {
|
||||||
|
// Dispatch "input" event with Event instance.
|
||||||
|
WidgetEvent widgetEvent(true, eUnidentifiedEvent);
|
||||||
|
widgetEvent.mSpecifiedEventType = nsGkAtoms::oninput;
|
||||||
|
widgetEvent.mFlags.mCancelable = false;
|
||||||
|
// Using same time as nsContentUtils::DispatchEvent() for backward
|
||||||
|
// compatibility.
|
||||||
|
widgetEvent.mTime = PR_Now();
|
||||||
|
(new AsyncEventDispatcher(aEventTargetElement,
|
||||||
|
widgetEvent))->RunDOMEventWhenSafe();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
nsCOMPtr<nsIWidget> widget;
|
||||||
|
if (aTextEditor) {
|
||||||
|
widget = aTextEditor->GetWidget();
|
||||||
|
if (NS_WARN_IF(!widget)) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nsIDocument* document = aEventTargetElement->OwnerDoc();
|
||||||
|
if (NS_WARN_IF(!document)) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
// If we're running xpcshell tests, we fail to get presShell here.
|
||||||
|
// Even in such case, we need to dispatch "input" event without widget.
|
||||||
|
nsIPresShell* presShell = document->GetShell();
|
||||||
|
if (presShell) {
|
||||||
|
nsPresContext* presContext = presShell->GetPresContext();
|
||||||
|
if (NS_WARN_IF(!presContext)) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
widget = presContext->GetRootWidget();
|
||||||
|
if (NS_WARN_IF(!widget)) {
|
||||||
|
return NS_ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch "input" event with InputEvent instance.
|
||||||
|
InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
|
||||||
|
|
||||||
|
// Using same time as old event dispatcher in EditorBase for backward
|
||||||
|
// compatibility.
|
||||||
|
inputEvent.mTime = static_cast<uint64_t>(PR_Now() / 1000);
|
||||||
|
|
||||||
|
// If there is an editor, set isComposing to true when it has composition.
|
||||||
|
// Note that EditorBase::IsIMEComposing() may return false even when we
|
||||||
|
// need to set it to true.
|
||||||
|
// Otherwise, i.e., editor hasn't been created for the element yet,
|
||||||
|
// we should set isComposing to false since the element can never has
|
||||||
|
// composition without editor.
|
||||||
|
inputEvent.mIsComposing =
|
||||||
|
aTextEditor ? !!aTextEditor->GetComposition() : false;
|
||||||
|
|
||||||
|
(new AsyncEventDispatcher(aEventTargetElement,
|
||||||
|
inputEvent))->RunDOMEventWhenSafe();
|
||||||
|
return NS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc,
|
nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc,
|
||||||
nsISupports *aTarget,
|
nsISupports *aTarget,
|
||||||
|
@ -123,6 +123,7 @@ class Dispatcher;
|
|||||||
class ErrorResult;
|
class ErrorResult;
|
||||||
class EventListenerManager;
|
class EventListenerManager;
|
||||||
class HTMLEditor;
|
class HTMLEditor;
|
||||||
|
class TextEditor;
|
||||||
|
|
||||||
namespace dom {
|
namespace dom {
|
||||||
class ContentFrameMessageManager;
|
class ContentFrameMessageManager;
|
||||||
@ -1377,9 +1378,12 @@ public:
|
|||||||
static void MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent);
|
static void MaybeFireNodeRemoved(nsINode* aChild, nsINode* aParent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method creates and dispatches a trusted event.
|
* These methods create and dispatch a trusted event.
|
||||||
* Works only with events which can be created by calling
|
* Works only with events which can be created by calling
|
||||||
* nsIDocument::CreateEvent() with parameter "Events".
|
* nsIDocument::CreateEvent() with parameter "Events".
|
||||||
|
* Note that don't use these methods for "input" event. Use
|
||||||
|
* DispatchInputEvent() instead.
|
||||||
|
*
|
||||||
* @param aDoc The document which will be used to create the event.
|
* @param aDoc The document which will be used to create the event.
|
||||||
* @param aTarget The target of the event, should be QIable to
|
* @param aTarget The target of the event, should be QIable to
|
||||||
* EventTarget.
|
* EventTarget.
|
||||||
@ -1438,6 +1442,26 @@ public:
|
|||||||
aDefaultAction, aOnlyChromeDispatch);
|
aDefaultAction, aOnlyChromeDispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method dispatches "input" event with proper event class. If it's
|
||||||
|
* unsafe to dispatch, this put the event into the script runner queue.
|
||||||
|
* Input Events spec defines as:
|
||||||
|
* Input events are dispatched on elements that act as editing hosts,
|
||||||
|
* including elements with the contenteditable attribute set, textarea
|
||||||
|
* elements, and input elements that permit text input.
|
||||||
|
*
|
||||||
|
* @param aEventTarget The event target element of the "input" event.
|
||||||
|
* Must not be nullptr.
|
||||||
|
* @param aTextEditor Optional. If this is called by editor,
|
||||||
|
* editor should set this. Otherwise, leave
|
||||||
|
* nullptr.
|
||||||
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
|
static nsresult DispatchInputEvent(Element* aEventTarget);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
|
static nsresult DispatchInputEvent(Element* aEventTarget,
|
||||||
|
mozilla::TextEditor* aTextEditor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method creates and dispatches a untrusted event.
|
* This method creates and dispatches a untrusted event.
|
||||||
* Works only with events which can be created by calling
|
* Works only with events which can be created by calling
|
||||||
|
@ -7048,6 +7048,11 @@ nsDOMAttributeMap::BlastSubtreeToPieces(nsINode* aNode)
|
|||||||
NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
|
NS_ASSERTION(NS_SUCCEEDED(rv), "Uh-oh, UnsetAttr shouldn't fail!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ShadowRoot* shadow = element->GetShadowRoot()) {
|
||||||
|
BlastSubtreeToPieces(shadow);
|
||||||
|
element->UnattachShadow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (aNode->HasChildren()) {
|
while (aNode->HasChildren()) {
|
||||||
|
@ -59,6 +59,16 @@ function onunload()
|
|||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aIsComposing, aDescription) {
|
||||||
|
if (aEvent.type != "input") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ok(aEvent instanceof InputEvent, `${aDescription}"input" event should be dispatched with InputEvent interface`);
|
||||||
|
is(aEvent.cancelable, false, `${aDescription}"input" event should be never cancelable`);
|
||||||
|
is(aEvent.bubbles, true, `${aDescription}"input" event should always bubble`);
|
||||||
|
is(aEvent.isComposing, aIsComposing, `${aDescription}isComposing should be ${aIsComposing}`);
|
||||||
|
}
|
||||||
|
|
||||||
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
|
const kIsMac = (navigator.platform.indexOf("Mac") == 0);
|
||||||
|
|
||||||
var iframe = document.getElementById("iframe");
|
var iframe = document.getElementById("iframe");
|
||||||
@ -222,6 +232,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be text");
|
description + "events[2] should be text");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], true, description);
|
||||||
TIP1.cancelComposition();
|
TIP1.cancelComposition();
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
|
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitComposition().
|
||||||
@ -257,6 +268,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be compositionend");
|
description + "events[1] should be compositionend");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
|
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
|
||||||
events = [];
|
events = [];
|
||||||
@ -304,6 +316,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[3] should be compositionend");
|
description + "events[3] should be compositionend");
|
||||||
is(events[4].type, "input",
|
is(events[4].type, "input",
|
||||||
description + "events[4] should be input");
|
description + "events[4] should be input");
|
||||||
|
checkInputEvent(events[4], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
|
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during cancelComposition().
|
||||||
events = [];
|
events = [];
|
||||||
@ -346,6 +359,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be compositionend");
|
description + "events[2] should be compositionend");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
|
// Let's check if beginInputTransaction() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
|
||||||
events = [];
|
events = [];
|
||||||
@ -385,6 +399,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be keypress");
|
description + "events[1] should be keypress");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
is(events[3].type, "keyup",
|
is(events[3].type, "keyup",
|
||||||
description + "events[3] should be keyup");
|
description + "events[3] should be keyup");
|
||||||
|
|
||||||
@ -442,6 +457,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be text");
|
description + "events[2] should be text");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], true, description);
|
||||||
TIP1.cancelComposition();
|
TIP1.cancelComposition();
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
|
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitComposition().
|
||||||
@ -477,6 +493,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be compositionend");
|
description + "events[1] should be compositionend");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
|
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during commitCompositionWith("bar").
|
||||||
events = [];
|
events = [];
|
||||||
@ -524,6 +541,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[3] should be compositionend");
|
description + "events[3] should be compositionend");
|
||||||
is(events[4].type, "input",
|
is(events[4].type, "input",
|
||||||
description + "events[4] should be input");
|
description + "events[4] should be input");
|
||||||
|
checkInputEvent(events[4], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
|
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during cancelComposition().
|
||||||
events = [];
|
events = [];
|
||||||
@ -566,6 +584,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be compositionend");
|
description + "events[2] should be compositionend");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
|
// Let's check if beginInputTransactionForTests() fails to steal the rights of TextEventDispatcher during keydown() and keyup().
|
||||||
events = [];
|
events = [];
|
||||||
@ -605,6 +624,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be keypress");
|
description + "events[1] should be keypress");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
is(events[3].type, "keyup",
|
is(events[3].type, "keyup",
|
||||||
description + "events[3] should be keyup");
|
description + "events[3] should be keyup");
|
||||||
|
|
||||||
@ -692,6 +712,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be text");
|
description + "events[2] should be text");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], true, description);
|
||||||
TIP1.cancelComposition();
|
TIP1.cancelComposition();
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
|
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
|
||||||
@ -745,6 +766,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be compositionend");
|
description + "events[1] should be compositionend");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
|
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
|
||||||
events = [];
|
events = [];
|
||||||
@ -822,6 +844,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[3] should be compositionend");
|
description + "events[3] should be compositionend");
|
||||||
is(events[4].type, "input",
|
is(events[4].type, "input",
|
||||||
description + "events[4] should be input");
|
description + "events[4] should be input");
|
||||||
|
checkInputEvent(events[4], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
|
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
|
||||||
events = [];
|
events = [];
|
||||||
@ -888,6 +911,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be compositionend");
|
description + "events[2] should be compositionend");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
|
// Let's check if beginInputTransaction() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
|
||||||
events = [];
|
events = [];
|
||||||
@ -951,6 +975,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be keypress");
|
description + "events[1] should be keypress");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
is(events[3].type, "keyup",
|
is(events[3].type, "keyup",
|
||||||
description + "events[3] should be keyup");
|
description + "events[3] should be keyup");
|
||||||
|
|
||||||
@ -1038,6 +1063,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be text");
|
description + "events[2] should be text");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], true, description);
|
||||||
TIP1.cancelComposition();
|
TIP1.cancelComposition();
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
|
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitComposition().
|
||||||
@ -1091,6 +1117,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be compositionend");
|
description + "events[1] should be compositionend");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
|
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during commitCompositionWith("bar");.
|
||||||
events = [];
|
events = [];
|
||||||
@ -1168,6 +1195,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[3] should be compositionend");
|
description + "events[3] should be compositionend");
|
||||||
is(events[4].type, "input",
|
is(events[4].type, "input",
|
||||||
description + "events[4] should be input");
|
description + "events[4] should be input");
|
||||||
|
checkInputEvent(events[4], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
|
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during cancelComposition();.
|
||||||
events = [];
|
events = [];
|
||||||
@ -1234,6 +1262,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[2] should be compositionend");
|
description + "events[2] should be compositionend");
|
||||||
is(events[3].type, "input",
|
is(events[3].type, "input",
|
||||||
description + "events[3] should be input");
|
description + "events[3] should be input");
|
||||||
|
checkInputEvent(events[3], false, description);
|
||||||
|
|
||||||
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
|
// Let's check if beginInputTransactionForTests() with another window fails to begin new input transaction with different TextEventDispatcher during keydown() and keyup();.
|
||||||
events = [];
|
events = [];
|
||||||
@ -1297,6 +1326,7 @@ function runBeginInputTransactionMethodTests()
|
|||||||
description + "events[1] should be keypress");
|
description + "events[1] should be keypress");
|
||||||
is(events[2].type, "input",
|
is(events[2].type, "input",
|
||||||
description + "events[2] should be input");
|
description + "events[2] should be input");
|
||||||
|
checkInputEvent(events[2], false, description);
|
||||||
is(events[3].type, "keyup",
|
is(events[3].type, "keyup",
|
||||||
description + "events[3] should be keyup");
|
description + "events[3] should be keyup");
|
||||||
|
|
||||||
|
@ -2328,6 +2328,9 @@ WebGLContext::GetVRFrame()
|
|||||||
already_AddRefed<layers::SharedSurfaceTextureClient>
|
already_AddRefed<layers::SharedSurfaceTextureClient>
|
||||||
WebGLContext::GetVRFrame()
|
WebGLContext::GetVRFrame()
|
||||||
{
|
{
|
||||||
|
if (!gl)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
EnsureVRReady();
|
EnsureVRReady();
|
||||||
/**
|
/**
|
||||||
* Swap buffers as though composition has occurred.
|
* Swap buffers as though composition has occurred.
|
||||||
@ -2337,9 +2340,6 @@ WebGLContext::GetVRFrame()
|
|||||||
BeginComposition();
|
BeginComposition();
|
||||||
EndComposition();
|
EndComposition();
|
||||||
|
|
||||||
if (!gl)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
gl::GLScreenBuffer* screen = gl->Screen();
|
gl::GLScreenBuffer* screen = gl->Screen();
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -251,16 +251,12 @@ public:
|
|||||||
Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
|
Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
nsresult
|
nsresult
|
||||||
DispatchEvents()
|
DispatchEvents()
|
||||||
{
|
{
|
||||||
nsresult rv = NS_OK;
|
nsresult rv = nsContentUtils::DispatchInputEvent(mInputElement);
|
||||||
rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
|
||||||
static_cast<Element*>(mInputElement.get()),
|
|
||||||
NS_LITERAL_STRING("input"),
|
|
||||||
CanBubble::eYes,
|
|
||||||
Cancelable::eNo);
|
|
||||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
|
|
||||||
|
|
||||||
rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
|
rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
|
||||||
static_cast<Element*>(mInputElement.get()),
|
static_cast<Element*>(mInputElement.get()),
|
||||||
@ -595,7 +591,9 @@ public:
|
|||||||
|
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD Update(const nsAString& aColor) override;
|
NS_IMETHOD Update(const nsAString& aColor) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD Done(const nsAString& aColor) override;
|
NS_IMETHOD Done(const nsAString& aColor) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -604,6 +602,7 @@ private:
|
|||||||
* If aTrustedUpdate is true, it will consider that aColor is a new value.
|
* If aTrustedUpdate is true, it will consider that aColor is a new value.
|
||||||
* Otherwise, it will check that aColor is different from the current value.
|
* Otherwise, it will check that aColor is different from the current value.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
|
nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
|
||||||
|
|
||||||
RefPtr<HTMLInputElement> mInput;
|
RefPtr<HTMLInputElement> mInput;
|
||||||
@ -634,15 +633,14 @@ nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valueChanged) {
|
if (!valueChanged) {
|
||||||
mValueChanged = true;
|
return NS_OK;
|
||||||
return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
|
|
||||||
static_cast<Element*>(mInput.get()),
|
|
||||||
NS_LITERAL_STRING("input"),
|
|
||||||
CanBubble::eYes,
|
|
||||||
Cancelable::eNo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mValueChanged = true;
|
||||||
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(mInput);
|
||||||
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
|
"Failed to dispatch input event");
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2346,6 +2344,10 @@ HTMLInputElement::SetUserInput(const nsAString& aValue,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isInputEventDispatchedByTextEditorState =
|
||||||
|
GetValueMode() == VALUE_MODE_VALUE &&
|
||||||
|
IsSingleLineTextControl(false);
|
||||||
|
|
||||||
nsresult rv =
|
nsresult rv =
|
||||||
SetValueInternal(aValue,
|
SetValueInternal(aValue,
|
||||||
nsTextEditorState::eSetValue_BySetUserInput |
|
nsTextEditorState::eSetValue_BySetUserInput |
|
||||||
@ -2353,13 +2355,11 @@ HTMLInputElement::SetUserInput(const nsAString& aValue,
|
|||||||
nsTextEditorState::eSetValue_MoveCursorToEndIfValueChanged);
|
nsTextEditorState::eSetValue_MoveCursorToEndIfValueChanged);
|
||||||
NS_ENSURE_SUCCESS_VOID(rv);
|
NS_ENSURE_SUCCESS_VOID(rv);
|
||||||
|
|
||||||
// FIXME: We're inconsistent about whether "input" events are cancelable or
|
if (!isInputEventDispatchedByTextEditorState) {
|
||||||
// not.
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
|
||||||
nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
static_cast<Element*>(this),
|
"Failed to dispatch input event");
|
||||||
NS_LITERAL_STRING("input"),
|
}
|
||||||
CanBubble::eYes,
|
|
||||||
Cancelable::eYes);
|
|
||||||
|
|
||||||
// If this element is not currently focused, it won't receive a change event for this
|
// If this element is not currently focused, it won't receive a change event for this
|
||||||
// update through the normal channels. So fire a change event immediately, instead.
|
// update through the normal channels. So fire a change event immediately, instead.
|
||||||
@ -2390,6 +2390,16 @@ HTMLInputElement::GetTextEditor()
|
|||||||
return GetTextEditorFromState();
|
return GetTextEditorFromState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP_(TextEditor*)
|
||||||
|
HTMLInputElement::GetTextEditorWithoutCreation()
|
||||||
|
{
|
||||||
|
nsTextEditorState* state = GetEditorState();
|
||||||
|
if (!state) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return state->GetTextEditorWithoutCreation();
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP_(nsISelectionController*)
|
NS_IMETHODIMP_(nsISelectionController*)
|
||||||
HTMLInputElement::GetSelectionController()
|
HTMLInputElement::GetSelectionController()
|
||||||
{
|
{
|
||||||
@ -2735,7 +2745,7 @@ HTMLInputElement::SetFiles(FileList* aFiles)
|
|||||||
/* static */ void
|
/* static */ void
|
||||||
HTMLInputElement::HandleNumberControlSpin(void* aData)
|
HTMLInputElement::HandleNumberControlSpin(void* aData)
|
||||||
{
|
{
|
||||||
HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
|
RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
|
||||||
|
|
||||||
NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
|
NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
|
||||||
"Should have called nsRepeatService::Stop()");
|
"Should have called nsRepeatService::Stop()");
|
||||||
@ -2806,6 +2816,11 @@ HTMLInputElement::SetValueInternal(const nsAString& aValue,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsSingleLineTextControl(false)) {
|
if (IsSingleLineTextControl(false)) {
|
||||||
|
// Note that if aFlags includes
|
||||||
|
// nsTextEditorState::eSetValue_BySetUserInput, "input" event is
|
||||||
|
// automatically dispatched by nsTextEditorState::SetValue().
|
||||||
|
// If you'd change condition of calling this method, you need to
|
||||||
|
// maintain SetUserInput() too.
|
||||||
if (!mInputData.mState->SetValue(value, aOldValue, aFlags)) {
|
if (!mInputData.mState->SetValue(value, aOldValue, aFlags)) {
|
||||||
return NS_ERROR_OUT_OF_MEMORY;
|
return NS_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
@ -3750,12 +3765,9 @@ HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
|
|||||||
if (frame) {
|
if (frame) {
|
||||||
frame->UpdateForValueChange();
|
frame->UpdateForValueChange();
|
||||||
}
|
}
|
||||||
RefPtr<AsyncEventDispatcher> asyncDispatcher =
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
|
||||||
new AsyncEventDispatcher(this,
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
NS_LITERAL_STRING("input"),
|
"Failed to dispatch input event");
|
||||||
CanBubble::eYes,
|
|
||||||
ChromeOnlyDispatch::eNo);
|
|
||||||
asyncDispatcher->RunDOMEventWhenSafe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3778,11 +3790,9 @@ HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (GetValueAsDecimal() != oldValue) {
|
if (GetValueAsDecimal() != oldValue) {
|
||||||
nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
|
||||||
static_cast<Element*>(this),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
NS_LITERAL_STRING("input"),
|
"Failed to dispatch input event");
|
||||||
CanBubble::eYes,
|
|
||||||
Cancelable::eNo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3877,11 +3887,9 @@ HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
|
|||||||
SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
|
SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
|
||||||
nsTextEditorState::eSetValue_Notify);
|
nsTextEditorState::eSetValue_Notify);
|
||||||
|
|
||||||
nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
|
||||||
static_cast<Element*>(this),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
NS_LITERAL_STRING("input"),
|
"Failed to dispatch input event");
|
||||||
CanBubble::eYes,
|
|
||||||
Cancelable::eNo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -4091,9 +4099,9 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fire input event and then change event.
|
// Fire input event and then change event.
|
||||||
nsContentUtils::DispatchTrustedEvent<InternalEditorInputEvent>
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
|
||||||
(OwnerDoc(), static_cast<Element*>(this),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
eEditorInput, CanBubble::eYes, Cancelable::eNo);
|
"Failed to dispatch input event");
|
||||||
|
|
||||||
nsContentUtils::DispatchTrustedEvent<WidgetEvent>
|
nsContentUtils::DispatchTrustedEvent<WidgetEvent>
|
||||||
(OwnerDoc(), static_cast<Element*>(this),
|
(OwnerDoc(), static_cast<Element*>(this),
|
||||||
|
@ -163,9 +163,11 @@ public:
|
|||||||
virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
|
virtual void AsyncEventRunning(AsyncEventDispatcher* aEvent) override;
|
||||||
|
|
||||||
// Overriden nsIFormControl methods
|
// Overriden nsIFormControl methods
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD Reset() override;
|
NS_IMETHOD Reset() override;
|
||||||
NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
|
NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
|
||||||
NS_IMETHOD SaveState() override;
|
NS_IMETHOD SaveState() override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual bool RestoreState(PresState* aState) override;
|
virtual bool RestoreState(PresState* aState) override;
|
||||||
virtual bool AllowDrop() override;
|
virtual bool AllowDrop() override;
|
||||||
virtual bool IsDisabledForEvents(WidgetEvent* aEvent) override;
|
virtual bool IsDisabledForEvents(WidgetEvent* aEvent) override;
|
||||||
@ -186,13 +188,20 @@ public:
|
|||||||
virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
|
virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const override;
|
||||||
|
|
||||||
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
|
void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult PreHandleEvent(EventChainVisitor& aVisitor) override;
|
virtual nsresult PreHandleEvent(EventChainVisitor& aVisitor) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult PostHandleEvent(
|
virtual nsresult PostHandleEvent(
|
||||||
EventChainPostVisitor& aVisitor) override;
|
EventChainPostVisitor& aVisitor) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor);
|
void PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void StartRangeThumbDrag(WidgetGUIEvent* aEvent);
|
void StartRangeThumbDrag(WidgetGUIEvent* aEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void FinishRangeThumbDrag(WidgetGUIEvent* aEvent = nullptr);
|
void FinishRangeThumbDrag(WidgetGUIEvent* aEvent = nullptr);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void CancelRangeThumbDrag(bool aIsForUserEvent = true);
|
void CancelRangeThumbDrag(bool aIsForUserEvent = true);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void SetValueOfRangeForUserEvent(Decimal aValue);
|
void SetValueOfRangeForUserEvent(Decimal aValue);
|
||||||
|
|
||||||
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
|
||||||
@ -200,6 +209,7 @@ public:
|
|||||||
virtual void UnbindFromTree(bool aDeep = true,
|
virtual void UnbindFromTree(bool aDeep = true,
|
||||||
bool aNullParent = true) override;
|
bool aNullParent = true) override;
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual void DoneCreatingElement() override;
|
virtual void DoneCreatingElement() override;
|
||||||
|
|
||||||
virtual EventStates IntrinsicState() const override;
|
virtual EventStates IntrinsicState() const override;
|
||||||
@ -223,10 +233,13 @@ public:
|
|||||||
NS_IMETHOD_(bool) ValueChanged() const override;
|
NS_IMETHOD_(bool) ValueChanged() const override;
|
||||||
NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
|
NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
|
||||||
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
|
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
|
||||||
|
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditorWithoutCreation() override;
|
||||||
NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
|
NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
|
||||||
NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
|
NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
|
||||||
NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
|
NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
|
NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD CreateEditor() override;
|
NS_IMETHOD CreateEditor() override;
|
||||||
NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
|
NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
|
||||||
NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override;
|
NS_IMETHOD_(void) SetPreviewValue(const nsAString& aValue) override;
|
||||||
@ -238,6 +251,7 @@ public:
|
|||||||
NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
|
NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
|
||||||
NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
|
NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
|
||||||
virtual void GetValueFromSetRangeText(nsAString& aValue) override;
|
virtual void GetValueFromSetRangeText(nsAString& aValue) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult SetValueFromSetRangeText(const nsAString& aValue) override;
|
virtual nsresult SetValueFromSetRangeText(const nsAString& aValue) override;
|
||||||
NS_IMETHOD_(bool) HasCachedSelection() override;
|
NS_IMETHOD_(bool) HasCachedSelection() override;
|
||||||
|
|
||||||
@ -277,6 +291,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
HTMLInputElement* GetSelectedRadioButton() const;
|
HTMLInputElement* GetSelectedRadioButton() const;
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
|
virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
|
||||||
|
|
||||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLInputElement,
|
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLInputElement,
|
||||||
@ -717,6 +732,7 @@ public:
|
|||||||
SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
|
SetHTMLAttr(nsGkAtoms::value, aValue, aRv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void SetValue(const nsAString& aValue, CallerType aCallerType,
|
void SetValue(const nsAString& aValue, CallerType aCallerType,
|
||||||
ErrorResult& aRv);
|
ErrorResult& aRv);
|
||||||
void GetValue(nsAString& aValue, CallerType aCallerType);
|
void GetValue(nsAString& aValue, CallerType aCallerType);
|
||||||
@ -891,12 +907,14 @@ public:
|
|||||||
};
|
};
|
||||||
void StopNumberControlSpinnerSpin(SpinnerStopState aState =
|
void StopNumberControlSpinnerSpin(SpinnerStopState aState =
|
||||||
eAllowDispatchingEvents);
|
eAllowDispatchingEvents);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void StepNumberControlForUserEvent(int32_t aDirection);
|
void StepNumberControlForUserEvent(int32_t aDirection);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callback function used by the nsRepeatService that we use to spin the
|
* The callback function used by the nsRepeatService that we use to spin the
|
||||||
* spinner for <input type=number>.
|
* spinner for <input type=number>.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
static void HandleNumberControlSpin(void* aData);
|
static void HandleNumberControlSpin(void* aData);
|
||||||
|
|
||||||
bool NumberSpinnerUpButtonIsDepressed() const
|
bool NumberSpinnerUpButtonIsDepressed() const
|
||||||
@ -916,6 +934,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
nsIEditor* GetEditor();
|
nsIEditor* GetEditor();
|
||||||
|
|
||||||
|
bool IsInputEventTarget() const
|
||||||
|
{
|
||||||
|
return IsSingleLineTextControl(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void SetUserInput(const nsAString& aInput,
|
void SetUserInput(const nsAString& aInput,
|
||||||
nsIPrincipal& aSubjectPrincipal);
|
nsIPrincipal& aSubjectPrincipal);
|
||||||
|
|
||||||
@ -1009,10 +1033,12 @@ protected:
|
|||||||
If previous value is unknown, aOldValue can be nullptr.
|
If previous value is unknown, aOldValue can be nullptr.
|
||||||
* @param aFlags See nsTextEditorState::SetValueFlags.
|
* @param aFlags See nsTextEditorState::SetValueFlags.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult SetValueInternal(const nsAString& aValue,
|
nsresult SetValueInternal(const nsAString& aValue,
|
||||||
const nsAString* aOldValue,
|
const nsAString* aOldValue,
|
||||||
uint32_t aFlags);
|
uint32_t aFlags);
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult SetValueInternal(const nsAString& aValue,
|
nsresult SetValueInternal(const nsAString& aValue,
|
||||||
uint32_t aFlags)
|
uint32_t aFlags)
|
||||||
{
|
{
|
||||||
@ -1054,6 +1080,7 @@ protected:
|
|||||||
/**
|
/**
|
||||||
* Called when an attribute has just been changed
|
* Called when an attribute has just been changed
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
virtual nsresult AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
||||||
const nsAttrValue* aValue,
|
const nsAttrValue* aValue,
|
||||||
const nsAttrValue* aOldValue,
|
const nsAttrValue* aOldValue,
|
||||||
@ -1180,6 +1207,7 @@ protected:
|
|||||||
/**
|
/**
|
||||||
* Manages the internal data storage across type changes.
|
* Manages the internal data storage across type changes.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void HandleTypeChange(uint8_t aNewType, bool aNotify);
|
void HandleTypeChange(uint8_t aNewType, bool aNotify);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1198,6 +1226,7 @@ protected:
|
|||||||
* @note You should not call this method if GetValueMode() doesn't return
|
* @note You should not call this method if GetValueMode() doesn't return
|
||||||
* VALUE_MODE_VALUE.
|
* VALUE_MODE_VALUE.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult SetDefaultValueAsValue();
|
nsresult SetDefaultValueAsValue();
|
||||||
|
|
||||||
void SetDirectionFromValue(bool aNotify);
|
void SetDirectionFromValue(bool aNotify);
|
||||||
|
@ -224,6 +224,12 @@ HTMLTextAreaElement::GetTextEditor()
|
|||||||
return mState.GetTextEditor();
|
return mState.GetTextEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP_(TextEditor*)
|
||||||
|
HTMLTextAreaElement::GetTextEditorWithoutCreation()
|
||||||
|
{
|
||||||
|
return mState.GetTextEditorWithoutCreation();
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP_(nsISelectionController*)
|
NS_IMETHODIMP_(nsISelectionController*)
|
||||||
HTMLTextAreaElement::GetSelectionController()
|
HTMLTextAreaElement::GetSelectionController()
|
||||||
{
|
{
|
||||||
|
@ -61,6 +61,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nsIFormControl
|
// nsIFormControl
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD Reset() override;
|
NS_IMETHOD Reset() override;
|
||||||
NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
|
NS_IMETHOD SubmitNamesValues(HTMLFormSubmission* aFormSubmission) override;
|
||||||
NS_IMETHOD SaveState() override;
|
NS_IMETHOD SaveState() override;
|
||||||
@ -83,10 +84,13 @@ public:
|
|||||||
NS_IMETHOD_(bool) ValueChanged() const override;
|
NS_IMETHOD_(bool) ValueChanged() const override;
|
||||||
NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
|
NS_IMETHOD_(void) GetTextEditorValue(nsAString& aValue, bool aIgnoreWrap) const override;
|
||||||
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
|
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() override;
|
||||||
|
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditorWithoutCreation() override;
|
||||||
NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
|
NS_IMETHOD_(nsISelectionController*) GetSelectionController() override;
|
||||||
NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
|
NS_IMETHOD_(nsFrameSelection*) GetConstFrameSelection() override;
|
||||||
NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
|
NS_IMETHOD BindToFrame(nsTextControlFrame* aFrame) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
|
NS_IMETHOD_(void) UnbindFromFrame(nsTextControlFrame* aFrame) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD CreateEditor() override;
|
NS_IMETHOD CreateEditor() override;
|
||||||
NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
|
NS_IMETHOD_(void) UpdateOverlayTextVisibility(bool aNotify) override;
|
||||||
NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
|
NS_IMETHOD_(bool) GetPlaceholderVisibility() override;
|
||||||
@ -98,6 +102,7 @@ public:
|
|||||||
NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
|
NS_IMETHOD_(void) InitializeKeyboardEventListeners() override;
|
||||||
NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
|
NS_IMETHOD_(void) OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) override;
|
||||||
virtual void GetValueFromSetRangeText(nsAString& aValue) override;
|
virtual void GetValueFromSetRangeText(nsAString& aValue) override;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult SetValueFromSetRangeText(const nsAString& aValue) override;
|
virtual nsresult SetValueFromSetRangeText(const nsAString& aValue) override;
|
||||||
NS_IMETHOD_(bool) HasCachedSelection() override;
|
NS_IMETHOD_(bool) HasCachedSelection() override;
|
||||||
|
|
||||||
@ -127,6 +132,7 @@ public:
|
|||||||
virtual void DoneAddingChildren(bool aHaveNotified) override;
|
virtual void DoneAddingChildren(bool aHaveNotified) override;
|
||||||
virtual bool IsDoneAddingChildren() override;
|
virtual bool IsDoneAddingChildren() override;
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
|
virtual nsresult Clone(dom::NodeInfo*, nsINode** aResult) const override;
|
||||||
|
|
||||||
nsresult CopyInnerTo(Element* aDest);
|
nsresult CopyInnerTo(Element* aDest);
|
||||||
@ -277,6 +283,7 @@ public:
|
|||||||
void GetDefaultValue(nsAString& aDefaultValue, ErrorResult& aError);
|
void GetDefaultValue(nsAString& aDefaultValue, ErrorResult& aError);
|
||||||
void SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aError);
|
void SetDefaultValue(const nsAString& aDefaultValue, ErrorResult& aError);
|
||||||
void GetValue(nsAString& aValue);
|
void GetValue(nsAString& aValue);
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void SetValue(const nsAString& aValue, ErrorResult& aError);
|
void SetValue(const nsAString& aValue, ErrorResult& aError);
|
||||||
|
|
||||||
uint32_t GetTextLength();
|
uint32_t GetTextLength();
|
||||||
@ -302,6 +309,12 @@ public:
|
|||||||
return mState.GetTextEditor();
|
return mState.GetTextEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsInputEventTarget() const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void SetUserInput(const nsAString& aValue,
|
void SetUserInput(const nsAString& aValue,
|
||||||
nsIPrincipal& aSubjectPrincipal);
|
nsIPrincipal& aSubjectPrincipal);
|
||||||
|
|
||||||
@ -358,6 +371,7 @@ protected:
|
|||||||
* @param aValue String to set.
|
* @param aValue String to set.
|
||||||
* @param aFlags See nsTextEditorState::SetValueFlags.
|
* @param aFlags See nsTextEditorState::SetValueFlags.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
|
nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,7 +126,8 @@ InputType::GetNonFileValueInternal(nsAString& aValue) const
|
|||||||
nsresult
|
nsresult
|
||||||
InputType::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
|
InputType::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
|
||||||
{
|
{
|
||||||
return mInputElement->SetValueInternal(aValue, aFlags);
|
RefPtr<mozilla::dom::HTMLInputElement> inputElement(mInputElement);
|
||||||
|
return inputElement->SetValueInternal(aValue, aFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
mozilla::Decimal
|
mozilla::Decimal
|
||||||
|
@ -130,6 +130,7 @@ protected:
|
|||||||
* @param aValue String to set.
|
* @param aValue String to set.
|
||||||
* @param aFlags See nsTextEditorState::SetValueFlags.
|
* @param aFlags See nsTextEditorState::SetValueFlags.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
|
nsresult SetValueInternal(const nsAString& aValue, uint32_t aFlags);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -67,6 +67,7 @@ public:
|
|||||||
return new (aMemory) RangeInputType(aInputElement);
|
return new (aMemory) RangeInputType(aInputElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult MinMaxStepAttrChanged() override;
|
nsresult MinMaxStepAttrChanged() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -102,8 +102,12 @@ public:
|
|||||||
* Get the editor object associated with the text editor.
|
* Get the editor object associated with the text editor.
|
||||||
* The return value is null if the control does not support an editor
|
* The return value is null if the control does not support an editor
|
||||||
* (for example, if it is a checkbox.)
|
* (for example, if it is a checkbox.)
|
||||||
|
* Note that GetTextEditor() creates editor if it hasn't been created yet.
|
||||||
|
* If you need editor only when the editor is there, you should use
|
||||||
|
* GetTextEditorWithoutCreation().
|
||||||
*/
|
*/
|
||||||
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() = 0;
|
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditor() = 0;
|
||||||
|
NS_IMETHOD_(mozilla::TextEditor*) GetTextEditorWithoutCreation() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the selection controller object associated with the text editor.
|
* Get the selection controller object associated with the text editor.
|
||||||
|
@ -61,10 +61,10 @@ SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags)
|
|||||||
return aEditorBase.SetFlags(aFlags);
|
return aEditorBase.SetFlags(aFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MOZ_STACK_CLASS ValueSetter
|
class MOZ_STACK_CLASS AutoInputEventSuppresser final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ValueSetter(TextEditor* aTextEditor)
|
explicit AutoInputEventSuppresser(TextEditor* aTextEditor)
|
||||||
: mTextEditor(aTextEditor)
|
: mTextEditor(aTextEditor)
|
||||||
// To protect against a reentrant call to SetValue, we check whether
|
// To protect against a reentrant call to SetValue, we check whether
|
||||||
// another SetValue is already happening for this editor. If it is,
|
// another SetValue is already happening for this editor. If it is,
|
||||||
@ -73,7 +73,7 @@ public:
|
|||||||
{
|
{
|
||||||
MOZ_ASSERT(aTextEditor);
|
MOZ_ASSERT(aTextEditor);
|
||||||
}
|
}
|
||||||
~ValueSetter()
|
~AutoInputEventSuppresser()
|
||||||
{
|
{
|
||||||
mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
|
mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
|
||||||
}
|
}
|
||||||
@ -1223,6 +1223,12 @@ nsTextEditorState::GetTextEditor()
|
|||||||
return mTextEditor;
|
return mTextEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextEditor*
|
||||||
|
nsTextEditorState::GetTextEditorWithoutCreation()
|
||||||
|
{
|
||||||
|
return mTextEditor;
|
||||||
|
}
|
||||||
|
|
||||||
nsISelectionController*
|
nsISelectionController*
|
||||||
nsTextEditorState::GetSelectionController() const
|
nsTextEditorState::GetSelectionController() const
|
||||||
{
|
{
|
||||||
@ -2394,6 +2400,10 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mTextCtrlElement may be cleared when we dispatch an event so that
|
||||||
|
// we should keep grabbing it with local variable.
|
||||||
|
nsCOMPtr<nsITextControlElement> textControlElement(mTextCtrlElement);
|
||||||
|
|
||||||
if (mTextEditor && mBoundFrame) {
|
if (mTextEditor && mBoundFrame) {
|
||||||
// The InsertText call below might flush pending notifications, which
|
// The InsertText call below might flush pending notifications, which
|
||||||
// could lead into a scheduled PrepareEditor to be called. That will
|
// could lead into a scheduled PrepareEditor to be called. That will
|
||||||
@ -2425,7 +2435,7 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
// this is necessary to avoid infinite recursion
|
// this is necessary to avoid infinite recursion
|
||||||
if (!currentValue.Equals(newValue)) {
|
if (!currentValue.Equals(newValue)) {
|
||||||
RefPtr<TextEditor> textEditor = mTextEditor;
|
RefPtr<TextEditor> textEditor = mTextEditor;
|
||||||
ValueSetter valueSetter(textEditor);
|
AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
|
||||||
|
|
||||||
nsCOMPtr<nsIDocument> document = textEditor->GetDocument();
|
nsCOMPtr<nsIDocument> document = textEditor->GetDocument();
|
||||||
if (NS_WARN_IF(!document)) {
|
if (NS_WARN_IF(!document)) {
|
||||||
@ -2448,8 +2458,6 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
valueSetter.Init();
|
|
||||||
|
|
||||||
// get the flags, remove readonly, disabled and max-length,
|
// get the flags, remove readonly, disabled and max-length,
|
||||||
// set the value, restore flags
|
// set the value, restore flags
|
||||||
{
|
{
|
||||||
@ -2464,10 +2472,16 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
// autocomplete, we need to replace the text as "insert string"
|
// autocomplete, we need to replace the text as "insert string"
|
||||||
// because undo should cancel only this operation (i.e., previous
|
// because undo should cancel only this operation (i.e., previous
|
||||||
// transactions typed by user shouldn't be merged with this).
|
// transactions typed by user shouldn't be merged with this).
|
||||||
|
// In this case, we need to dispatch "input" event because
|
||||||
|
// web apps may need to know the user's operation.
|
||||||
DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
|
DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
|
||||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||||
"Failed to set the new value");
|
"Failed to set the new value");
|
||||||
} else if (aFlags & eSetValue_ForXUL) {
|
} else if (aFlags & eSetValue_ForXUL) {
|
||||||
|
// When setting value of XUL <textbox>, we shouldn't dispatch
|
||||||
|
// "input" event.
|
||||||
|
suppressInputEventDispatching.Init();
|
||||||
|
|
||||||
// On XUL <textbox> element, we need to preserve existing undo
|
// On XUL <textbox> element, we need to preserve existing undo
|
||||||
// transactions.
|
// transactions.
|
||||||
// XXX Do we really need to do such complicated optimization?
|
// XXX Do we really need to do such complicated optimization?
|
||||||
@ -2505,6 +2519,10 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
"Failed to insert the new value");
|
"Failed to insert the new value");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// When setting value of <input>, we shouldn't dispatch "input"
|
||||||
|
// event.
|
||||||
|
suppressInputEventDispatching.Init();
|
||||||
|
|
||||||
// On <input> or <textarea>, we shouldn't preserve existing undo
|
// On <input> or <textarea>, we shouldn't preserve existing undo
|
||||||
// transactions because other browsers do not preserve them too
|
// transactions because other browsers do not preserve them too
|
||||||
// and not preserving transactions makes setting value faster.
|
// and not preserving transactions makes setting value faster.
|
||||||
@ -2584,6 +2602,18 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
if (mBoundFrame) {
|
if (mBoundFrame) {
|
||||||
mBoundFrame->UpdateValueDisplay(true);
|
mBoundFrame->UpdateValueDisplay(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is called as part of user input, we need to dispatch "input"
|
||||||
|
// event since web apps may want to know the user operation.
|
||||||
|
if (aFlags & eSetValue_BySetUserInput) {
|
||||||
|
nsCOMPtr<Element> element = do_QueryInterface(textControlElement);
|
||||||
|
MOZ_ASSERT(element);
|
||||||
|
RefPtr<TextEditor> textEditor;
|
||||||
|
DebugOnly<nsresult> rvIgnored =
|
||||||
|
nsContentUtils::DispatchInputEvent(element, textEditor);
|
||||||
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
|
"Failed to dispatch input event");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Even if our value is not actually changing, apparently we need to mark
|
// Even if our value is not actually changing, apparently we need to mark
|
||||||
// our SelectionProperties dirty to make accessibility tests happy.
|
// our SelectionProperties dirty to make accessibility tests happy.
|
||||||
@ -2600,8 +2630,10 @@ nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
|
|||||||
ValueWasChanged(!!mBoundFrame);
|
ValueWasChanged(!!mBoundFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
|
// XXX Should we stop notifying "value changed" if mTextCtrlElement has
|
||||||
/* aWasInteractiveUserChange = */ false);
|
// been cleared?
|
||||||
|
textControlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
|
||||||
|
/* aWasInteractiveUserChange = */ false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -152,10 +152,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
mozilla::TextEditor* GetTextEditor();
|
mozilla::TextEditor* GetTextEditor();
|
||||||
|
mozilla::TextEditor* GetTextEditorWithoutCreation();
|
||||||
nsISelectionController* GetSelectionController() const;
|
nsISelectionController* GetSelectionController() const;
|
||||||
nsFrameSelection* GetConstFrameSelection();
|
nsFrameSelection* GetConstFrameSelection();
|
||||||
nsresult BindToFrame(nsTextControlFrame* aFrame);
|
nsresult BindToFrame(nsTextControlFrame* aFrame);
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void UnbindFromFrame(nsTextControlFrame* aFrame);
|
void UnbindFromFrame(nsTextControlFrame* aFrame);
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
nsresult PrepareEditor(const nsAString *aValue = nullptr);
|
nsresult PrepareEditor(const nsAString *aValue = nullptr);
|
||||||
void InitializeKeyboardEventListeners();
|
void InitializeKeyboardEventListeners();
|
||||||
|
|
||||||
@ -181,9 +184,11 @@ public:
|
|||||||
// undo history.
|
// undo history.
|
||||||
eSetValue_ForXUL = 1 << 4,
|
eSetValue_ForXUL = 1 << 4,
|
||||||
};
|
};
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
MOZ_MUST_USE bool SetValue(const nsAString& aValue,
|
MOZ_MUST_USE bool SetValue(const nsAString& aValue,
|
||||||
const nsAString* aOldValue,
|
const nsAString* aOldValue,
|
||||||
uint32_t aFlags);
|
uint32_t aFlags);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
MOZ_MUST_USE bool SetValue(const nsAString& aValue,
|
MOZ_MUST_USE bool SetValue(const nsAString& aValue,
|
||||||
uint32_t aFlags)
|
uint32_t aFlags)
|
||||||
{
|
{
|
||||||
@ -303,6 +308,7 @@ public:
|
|||||||
// Sync up our selection properties with our editor prior to being destroyed.
|
// Sync up our selection properties with our editor prior to being destroyed.
|
||||||
// This will invoke UnbindFromFrame() to ensure that we grab whatever
|
// This will invoke UnbindFromFrame() to ensure that we grab whatever
|
||||||
// selection state may be at the moment.
|
// selection state may be at the moment.
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void SyncUpSelectionPropertiesBeforeDestruction();
|
void SyncUpSelectionPropertiesBeforeDestruction();
|
||||||
|
|
||||||
// Get the selection range start and end points in our text.
|
// Get the selection range start and end points in our text.
|
||||||
|
@ -82,6 +82,7 @@ skip-if = os == "android" && debug # bug 1397615
|
|||||||
[test_meter_element.html]
|
[test_meter_element.html]
|
||||||
[test_meter_pseudo-classes.html]
|
[test_meter_pseudo-classes.html]
|
||||||
[test_min_attribute.html]
|
[test_min_attribute.html]
|
||||||
|
[test_MozEditableElement_setUserInput.html]
|
||||||
[test_mozistextfield.html]
|
[test_mozistextfield.html]
|
||||||
[test_novalidate_attribute.html]
|
[test_novalidate_attribute.html]
|
||||||
[test_option_disabled.html]
|
[test_option_disabled.html]
|
||||||
|
239
dom/html/test/forms/test_MozEditableElement_setUserInput.html
Normal file
239
dom/html/test/forms/test_MozEditableElement_setUserInput.html
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
<!DOCTYPE>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Test for MozEditableElement.setUserInput()</title>
|
||||||
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||||
|
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="display">
|
||||||
|
</div>
|
||||||
|
<div id="content"></div>
|
||||||
|
<pre id="test">
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<script class="testbody" type="application/javascript">
|
||||||
|
SimpleTest.waitForExplicitFinish();
|
||||||
|
SimpleTest.waitForFocus(() => {
|
||||||
|
let content = document.getElementById("content");
|
||||||
|
/**
|
||||||
|
* Test structure:
|
||||||
|
* element: the tag name to create.
|
||||||
|
* type: the type attribute value for the element. If unnecessary omit it.
|
||||||
|
* input: the values calling setUserInput() with.
|
||||||
|
* before: used when calling setUserInput() before the element gets focus.
|
||||||
|
* after: used when calling setUserInput() after the element gets focus.
|
||||||
|
* result: the results of calling setUserInput().
|
||||||
|
* before: the element's expected value of calling setUserInput() before the element gets focus.
|
||||||
|
* after: the element's expected value of calling setUserInput() after the element gets focus.
|
||||||
|
* fireInputEvent: true if "input" event should be fired. Otherwise, false.
|
||||||
|
*/
|
||||||
|
for (let test of [{element: "input", type: "hidden",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: false}},
|
||||||
|
{element: "input", type: "text",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "search",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "tel",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "url",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "email",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "password",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "date" does not support setUserInput, but dispatches "input" event...
|
||||||
|
{element: "input", type: "date",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "month" does not support setUserInput, but dispatches "input" event...
|
||||||
|
{element: "input", type: "month",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "week" does not support setUserInput, but dispatches "input" event...
|
||||||
|
{element: "input", type: "week",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "time" does not support setUserInput, but dispatches "input" event...
|
||||||
|
{element: "input", type: "time",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "datetime-local" does not support setUserInput, but dispatches "input" event...
|
||||||
|
{element: "input", type: "datetime-local",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "number",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "range",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "color" does not support setUserInput, but dispatches "input" event...
|
||||||
|
{element: "input", type: "color",
|
||||||
|
input: {before: "#5C5C5C", after: "#FFFFFF"},
|
||||||
|
result: {before: "#5c5c5c", after:"#ffffff", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "checkbox",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "radio",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}},
|
||||||
|
// "file" is not supported by setUserInput? But there is a path...
|
||||||
|
{element: "input", type: "file",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "", after:"", fireInputEvent: true}},
|
||||||
|
{element: "input", type: "submit",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: false}},
|
||||||
|
{element: "input", type: "image",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: false}},
|
||||||
|
{element: "input", type: "reset",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: false}},
|
||||||
|
{element: "input", type: "button",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: false}},
|
||||||
|
{element: "textarea",
|
||||||
|
input: {before: "3", after: "6"},
|
||||||
|
result: {before: "3", after:"6", fireInputEvent: true}}]) {
|
||||||
|
let tag =
|
||||||
|
test.type !== undefined ? `<${test.element} type="${test.type}">` :
|
||||||
|
`<${test.element}>`;
|
||||||
|
content.innerHTML =
|
||||||
|
test.element !== "input" ? tag : `${tag}</${test.element}>`;
|
||||||
|
content.scrollTop; // Flush pending layout.
|
||||||
|
let target = content.firstChild;
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
target.addEventListener("input", onInput);
|
||||||
|
|
||||||
|
// Before setting focus, editor of the element may have not been created yet.
|
||||||
|
let previousValue = target.value;
|
||||||
|
SpecialPowers.wrap(target).setUserInput(test.input.before);
|
||||||
|
if (target.value == previousValue && test.result.before != previousValue) {
|
||||||
|
todo_is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
|
||||||
|
} else {
|
||||||
|
is(target.value, test.result.before, `setUserInput("${test.input.before}") before ${tag} gets focus should set its value to "${test.result.before}"`);
|
||||||
|
}
|
||||||
|
if (target.value == previousValue) {
|
||||||
|
if (test.type === "date" || test.type === "time") {
|
||||||
|
todo_is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
} else {
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
} else if (!test.result.fireInputEvent) {
|
||||||
|
// HTML spec defines that "input" elements whose type are "hidden",
|
||||||
|
// "submit", "image", "reset" and "button" shouldn't fire input event
|
||||||
|
// when its value is changed.
|
||||||
|
// XXX Perhaps, we shouldn't support setUserInput() with such types.
|
||||||
|
if (test.type === "hidden" ||
|
||||||
|
test.type === "submit" ||
|
||||||
|
test.type === "image" ||
|
||||||
|
test.type === "reset" ||
|
||||||
|
test.type === "button") {
|
||||||
|
todo_is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
} else {
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be dispatched when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
if (SpecialPowers.wrap(target).isInputEventTarget) {
|
||||||
|
if (test.type === "number" || test.type === "time") {
|
||||||
|
todo(inputEvents[0] instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
} else {
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
|
||||||
|
`"input" event should be dispatched with Event interface when setUserInput("${test.input.before}") is called before ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
`"input" event should be never cancelable (${tag}, before getting focus)`);
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
`"input" event should always bubble (${tag}, before getting focus)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
|
target.focus();
|
||||||
|
previousValue = target.value;
|
||||||
|
SpecialPowers.wrap(target).setUserInput(test.input.after);
|
||||||
|
if (target.value == previousValue && test.result.after != previousValue) {
|
||||||
|
todo_is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
|
||||||
|
} else {
|
||||||
|
is(target.value, test.result.after, `setUserInput("${test.input.after}") after ${tag} gets focus should set its value to "${test.result.after}"`);
|
||||||
|
}
|
||||||
|
if (target.value == previousValue) {
|
||||||
|
if (test.type === "date" || test.type === "time") {
|
||||||
|
todo_is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
} else {
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
} else if (!test.result.fireInputEvent) {
|
||||||
|
// HTML spec defines that "input" elements whose type are "hidden",
|
||||||
|
// "submit", "image", "reset" and "button" shouldn't fire input event
|
||||||
|
// when its value is changed.
|
||||||
|
// XXX Perhaps, we shouldn't support setUserInput() with such types.
|
||||||
|
if (test.type === "hidden" ||
|
||||||
|
test.type === "submit" ||
|
||||||
|
test.type === "image" ||
|
||||||
|
test.type === "reset" ||
|
||||||
|
test.type === "button") {
|
||||||
|
todo_is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
} else {
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be dispatched when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
if (SpecialPowers.wrap(target).isInputEventTarget) {
|
||||||
|
if (test.type === "number" || test.type === "time") {
|
||||||
|
todo(inputEvents[0] instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
} else {
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ok(inputEvents[0] instanceof Event && !(inputEvents[0] instanceof UIEvent),
|
||||||
|
`"input" event should be dispatched with Event interface when setUserInput("${test.input.after}") is called after ${tag} gets focus`);
|
||||||
|
}
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
`"input" event should be never cancelable (${tag}, after getting focus)`);
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
`"input" event should always bubble (${tag}, after getting focus)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
target.removeEventListener("input", onInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleTest.finish();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -13,24 +13,24 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=851780">Mozilla Bug 851780</a>
|
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=851780">Mozilla Bug 851780</a>
|
||||||
<p id="display"></p>
|
<p id="display"></p>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<input type="file" id="fileInput"></input>
|
<input type="file" id="fileInput">
|
||||||
<textarea id="textarea" oninput="++textareaInput;"></textarea>
|
<textarea id="textarea"></textarea>
|
||||||
<input type="text" id="input_text" oninput="++textInput[0];"></input>
|
<input type="text" id="input_text">
|
||||||
<input type="email" id="input_email" oninput="++textInput[1];"></input>
|
<input type="email" id="input_email">
|
||||||
<input type="search" id="input_search" oninput="++textInput[2];"></input>
|
<input type="search" id="input_search">
|
||||||
<input type="tel" id="input_tel" oninput="++textInput[3];"></input>
|
<input type="tel" id="input_tel">
|
||||||
<input type="url" id="input_url" oninput="++textInput[4];"></input>
|
<input type="url" id="input_url">
|
||||||
<input type="password" id="input_password" oninput="++textInput[5];"></input>
|
<input type="password" id="input_password">
|
||||||
|
|
||||||
<!-- "Non-text" inputs-->
|
<!-- "Non-text" inputs-->
|
||||||
<input type="button" id="input_button" oninput="++NonTextInput[0];"></input>
|
<input type="button" id="input_button">
|
||||||
<input type="submit" id="input_submit" oninput="++NonTextInput[1];"></input>
|
<input type="submit" id="input_submit">
|
||||||
<input type="image" id="input_image" oninput="++NonTextInput[2];"></input>
|
<input type="image" id="input_image">
|
||||||
<input type="reset" id="input_reset" oninput="++NonTextInput[3];"></input>
|
<input type="reset" id="input_reset">
|
||||||
<input type="radio" id="input_radio" oninput="++NonTextInput[4];"></input>
|
<input type="radio" id="input_radio">
|
||||||
<input type="checkbox" id="input_checkbox" oninput="++NonTextInput[5];"></input>
|
<input type="checkbox" id="input_checkbox">
|
||||||
<input type="range" id="input_range" oninput="++rangeInput;"></input>
|
<input type="range" id="input_range">
|
||||||
<input type="number" id="input_number" oninput="++numberInput;"></input>
|
<input type="number" id="input_number">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<pre id="test">
|
<pre id="test">
|
||||||
@ -40,18 +40,68 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||||||
|
|
||||||
const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
|
const isDesktop = !/Mobile|Tablet/.test(navigator.userAgent);
|
||||||
|
|
||||||
var textareaInput = 0;
|
function checkIfInputIsInputEvent(aEvent, aToDo, aDescription) {
|
||||||
|
if (aToDo) {
|
||||||
|
// Probably, key operation should fire "input" event with InputEvent interface.
|
||||||
|
// See https://github.com/w3c/input-events/issues/88
|
||||||
|
todo(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
} else {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
}
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Those are types were the input event apply.
|
function checkIfInputIsEvent(aEvent, aDescription) {
|
||||||
|
ok(event instanceof Event && !(event instanceof UIEvent),
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
var textareaInput = 0;
|
||||||
|
document.getElementById("textarea").oninput = (aEvent) => {
|
||||||
|
++textareaInput;
|
||||||
|
checkIfInputIsInputEvent(aEvent, false, "on textarea element");
|
||||||
|
};
|
||||||
|
|
||||||
|
// These are the type were the input event apply.
|
||||||
var textTypes = ["text", "email", "search", "tel", "url", "password"];
|
var textTypes = ["text", "email", "search", "tel", "url", "password"];
|
||||||
var textInput = [0, 0, 0, 0, 0, 0];
|
var textInput = [0, 0, 0, 0, 0, 0];
|
||||||
|
for (let id of ["input_text", "input_email", "input_search", "input_tel", "input_url", "input_password"]) {
|
||||||
|
document.getElementById(id).oninput = (aEvent) => {
|
||||||
|
++textInput[textTypes.indexOf(aEvent.target.type)];
|
||||||
|
checkIfInputIsInputEvent(aEvent, false, `on input element whose type is ${aEvent.target.type}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Those are events were the input event does not apply.
|
// These are the type were the input event does not apply.
|
||||||
var NonTextTypes = ["button", "submit", "image", "reset", "radio", "checkbox"];
|
var NonTextTypes = ["button", "submit", "image", "reset", "radio", "checkbox"];
|
||||||
var NonTextInput = [0, 0, 0, 0, 0, 0];
|
var NonTextInput = [0, 0, 0, 0, 0, 0];
|
||||||
|
for (let id of ["input_button", "input_submit", "input_image", "input_reset", "input_radio", "input_checkbox"]) {
|
||||||
|
document.getElementById(id).oninput = (aEvent) => {
|
||||||
|
++NonTextInput[NonTextTypes.indexOf(aEvent.target.type)];
|
||||||
|
checkIfInputIsEvent(aEvent, `on input element whose type is ${aEvent.target.type}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var rangeInput = 0;
|
var rangeInput = 0;
|
||||||
|
document.getElementById("input_range").oninput = (aEvent) => {
|
||||||
|
++rangeInput;
|
||||||
|
checkIfInputIsEvent(aEvent, "on input element whose type is range");
|
||||||
|
};
|
||||||
|
|
||||||
var numberInput = 0;
|
var numberInput = 0;
|
||||||
|
document.getElementById("input_number").oninput = (aEvent) => {
|
||||||
|
++numberInput;
|
||||||
|
checkIfInputIsInputEvent(aEvent, true, "on input element whose type is number");
|
||||||
|
};
|
||||||
|
|
||||||
SimpleTest.waitForExplicitFinish();
|
SimpleTest.waitForExplicitFinish();
|
||||||
var MockFilePicker = SpecialPowers.MockFilePicker;
|
var MockFilePicker = SpecialPowers.MockFilePicker;
|
||||||
@ -66,6 +116,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=851780
|
|||||||
|
|
||||||
input.addEventListener("input", function (aEvent) {
|
input.addEventListener("input", function (aEvent) {
|
||||||
ok(true, "input event should have been dispatched on file input.");
|
ok(true, "input event should have been dispatched on file input.");
|
||||||
|
checkIfInputIsEvent(aEvent, "on file input");
|
||||||
});
|
});
|
||||||
|
|
||||||
input.click();
|
input.click();
|
||||||
|
@ -48,6 +48,14 @@ const SPIN_UP_Y = 3;
|
|||||||
const SPIN_DOWN_X = inputRect.width - 3;
|
const SPIN_DOWN_X = inputRect.width - 3;
|
||||||
const SPIN_DOWN_Y = inputRect.height - 3;
|
const SPIN_DOWN_Y = inputRect.height - 3;
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
// Probably, key operation should fire "input" event with InputEvent interface.
|
||||||
|
// See https://github.com/w3c/input-events/issues/88
|
||||||
|
todo(aEvent instanceof InputEvent, `"input" event should be dispatched with InputEvent interface on input element whose type is number ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false, `"input" event should be never cancelable on input element whose type is number ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true, `"input" event should always bubble on input element whose type is number ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
input.value = 0;
|
input.value = 0;
|
||||||
|
|
||||||
@ -134,6 +142,7 @@ var spinTests = [
|
|||||||
input.value = 0;
|
input.value = 0;
|
||||||
input.addEventListener("input", function(evt) {
|
input.addEventListener("input", function(evt) {
|
||||||
++inputEventCount;
|
++inputEventCount;
|
||||||
|
checkInputEvent(event, "#1");
|
||||||
if (inputEventCount == 3) {
|
if (inputEventCount == 3) {
|
||||||
is(input.value, "3", "Testing spin-up button");
|
is(input.value, "3", "Testing spin-up button");
|
||||||
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousemove" });
|
synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousemove" });
|
||||||
@ -154,6 +163,7 @@ var spinTests = [
|
|||||||
input.value = 0;
|
input.value = 0;
|
||||||
input.addEventListener("input", function(evt) {
|
input.addEventListener("input", function(evt) {
|
||||||
++inputEventCount;
|
++inputEventCount;
|
||||||
|
checkInputEvent(event, "#2");
|
||||||
if (inputEventCount == 3) {
|
if (inputEventCount == 3) {
|
||||||
is(input.value, "-3", "Testing spin-down button");
|
is(input.value, "-3", "Testing spin-down button");
|
||||||
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousemove" });
|
synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousemove" });
|
||||||
@ -174,6 +184,7 @@ var spinTests = [
|
|||||||
input.value = 0;
|
input.value = 0;
|
||||||
input.addEventListener("input", function(evt) {
|
input.addEventListener("input", function(evt) {
|
||||||
++inputEventCount;
|
++inputEventCount;
|
||||||
|
checkInputEvent(event, "#3");
|
||||||
if (inputEventCount == 3) {
|
if (inputEventCount == 3) {
|
||||||
synthesizeMouse(input, -1, -1, { type: "mousemove" });
|
synthesizeMouse(input, -1, -1, { type: "mousemove" });
|
||||||
var eventHandler = arguments.callee;
|
var eventHandler = arguments.callee;
|
||||||
@ -195,6 +206,7 @@ var spinTests = [
|
|||||||
input.value = 0;
|
input.value = 0;
|
||||||
input.addEventListener("input", function(evt) {
|
input.addEventListener("input", function(evt) {
|
||||||
++inputEventCount;
|
++inputEventCount;
|
||||||
|
checkInputEvent(event, "#4");
|
||||||
if (inputEventCount == 3) {
|
if (inputEventCount == 3) {
|
||||||
input.type = "text"
|
input.type = "text"
|
||||||
var eventHandler = arguments.callee;
|
var eventHandler = arguments.callee;
|
||||||
|
@ -192,6 +192,12 @@ interface MozEditableElement {
|
|||||||
[Pure, ChromeOnly]
|
[Pure, ChromeOnly]
|
||||||
readonly attribute nsIEditor? editor;
|
readonly attribute nsIEditor? editor;
|
||||||
|
|
||||||
|
// This is set to true if "input" event should be fired with InputEvent on
|
||||||
|
// the element. Otherwise, i.e., if "input" event should be fired with
|
||||||
|
// Event, set to false.
|
||||||
|
[Func="IsChromeOrXBLOrUAWidget"]
|
||||||
|
readonly attribute boolean isInputEventTarget;
|
||||||
|
|
||||||
// This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
|
// This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
|
||||||
// of the value change is closer to the normal user input, so 'change' event
|
// of the value change is closer to the normal user input, so 'change' event
|
||||||
// for example will be dispatched when focusing out the element.
|
// for example will be dispatched when focusing out the element.
|
||||||
|
@ -2142,56 +2142,6 @@ EditorBase::NotifySelectionChanged(nsIDocument* aDocument,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditorInputEventDispatcher final : public Runnable
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
EditorInputEventDispatcher(EditorBase* aEditorBase,
|
|
||||||
nsIContent* aTarget,
|
|
||||||
bool aIsComposing)
|
|
||||||
: Runnable("EditorInputEventDispatcher")
|
|
||||||
, mEditorBase(aEditorBase)
|
|
||||||
, mTarget(aTarget)
|
|
||||||
, mIsComposing(aIsComposing)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
NS_IMETHOD Run() override
|
|
||||||
{
|
|
||||||
// Note that we don't need to check mDispatchInputEvent here. We need
|
|
||||||
// to check it only when the editor requests to dispatch the input event.
|
|
||||||
|
|
||||||
if (!mTarget->IsInComposedDoc()) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsCOMPtr<nsIPresShell> ps = mEditorBase->GetPresShell();
|
|
||||||
if (!ps) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsCOMPtr<nsIWidget> widget = mEditorBase->GetWidget();
|
|
||||||
if (!widget) {
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Even if the change is caused by untrusted event, we need to dispatch
|
|
||||||
// trusted input event since it's a fact.
|
|
||||||
InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
|
|
||||||
inputEvent.mTime = static_cast<uint64_t>(PR_Now() / 1000);
|
|
||||||
inputEvent.mIsComposing = mIsComposing;
|
|
||||||
nsEventStatus status = nsEventStatus_eIgnore;
|
|
||||||
nsresult rv =
|
|
||||||
ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status);
|
|
||||||
NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error
|
|
||||||
return NS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
RefPtr<EditorBase> mEditorBase;
|
|
||||||
nsCOMPtr<nsIContent> mTarget;
|
|
||||||
bool mIsComposing;
|
|
||||||
};
|
|
||||||
|
|
||||||
void
|
void
|
||||||
EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification)
|
EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification)
|
||||||
{
|
{
|
||||||
@ -2252,19 +2202,15 @@ EditorBase::NotifyEditorObservers(NotificationForEditorObservers aNotification)
|
|||||||
void
|
void
|
||||||
EditorBase::FireInputEvent()
|
EditorBase::FireInputEvent()
|
||||||
{
|
{
|
||||||
// We don't need to dispatch multiple input events if there is a pending
|
RefPtr<Element> targetElement = GetInputEventTargetElement();
|
||||||
// input event. However, it may have different event target. If we resolved
|
if (NS_WARN_IF(!targetElement)) {
|
||||||
// this issue, we need to manage the pending events in an array. But it's
|
return;
|
||||||
// overwork. We don't need to do it for the very rare case.
|
}
|
||||||
|
RefPtr<TextEditor> textEditor = AsTextEditor();
|
||||||
nsCOMPtr<nsIContent> target = GetInputEventTargetContent();
|
DebugOnly<nsresult> rvIgnored =
|
||||||
NS_ENSURE_TRUE_VOID(target);
|
nsContentUtils::DispatchInputEvent(targetElement, textEditor);
|
||||||
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
// NOTE: Don't refer IsIMEComposing() because it returns false even before
|
"Failed to dispatch input event");
|
||||||
// compositionend. However, DOM Level 3 Events defines it should be
|
|
||||||
// true after compositionstart and before compositionend.
|
|
||||||
nsContentUtils::AddScriptRunner(
|
|
||||||
new EditorInputEventDispatcher(this, target, !!GetComposition()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
|
@ -356,6 +356,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* ToggleTextDirection() toggles text-direction of the root element.
|
* ToggleTextDirection() toggles text-direction of the root element.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
nsresult ToggleTextDirection();
|
nsresult ToggleTextDirection();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -367,6 +368,7 @@ public:
|
|||||||
eLTR,
|
eLTR,
|
||||||
eRTL,
|
eRTL,
|
||||||
};
|
};
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void SwitchTextDirectionTo(TextDirection aTextDirection);
|
void SwitchTextDirectionTo(TextDirection aTextDirection);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1732,7 +1734,9 @@ protected: // Called by helper classes.
|
|||||||
* can later merge, if needed. Merging is unavailable between transaction
|
* can later merge, if needed. Merging is unavailable between transaction
|
||||||
* manager batches.
|
* manager batches.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void BeginPlaceholderTransaction(nsAtom* aTransactionName);
|
void BeginPlaceholderTransaction(nsAtom* aTransactionName);
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
void EndPlaceholderTransaction();
|
void EndPlaceholderTransaction();
|
||||||
|
|
||||||
void BeginUpdateViewBatch();
|
void BeginUpdateViewBatch();
|
||||||
@ -1768,6 +1772,8 @@ protected: // Shouldn't be used by friend classes
|
|||||||
virtual nsresult SelectAllInternal();
|
virtual nsresult SelectAllInternal();
|
||||||
|
|
||||||
nsresult DetermineCurrentDirection();
|
nsresult DetermineCurrentDirection();
|
||||||
|
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void FireInputEvent();
|
void FireInputEvent();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1888,7 +1894,7 @@ protected: // Shouldn't be used by friend classes
|
|||||||
/**
|
/**
|
||||||
* Get the input event target. This might return null.
|
* Get the input event target. This might return null.
|
||||||
*/
|
*/
|
||||||
virtual already_AddRefed<nsIContent> GetInputEventTargetContent() = 0;
|
virtual already_AddRefed<Element> GetInputEventTargetElement() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return true if spellchecking should be enabled for this editor.
|
* Return true if spellchecking should be enabled for this editor.
|
||||||
@ -1950,6 +1956,7 @@ protected: // Shouldn't be used by friend classes
|
|||||||
eNotifyEditorObserversOfBefore,
|
eNotifyEditorObserversOfBefore,
|
||||||
eNotifyEditorObserversOfCancel
|
eNotifyEditorObserversOfCancel
|
||||||
};
|
};
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void NotifyEditorObservers(NotificationForEditorObservers aNotification);
|
void NotifyEditorObservers(NotificationForEditorObservers aNotification);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -1031,7 +1031,7 @@ EditorEventListener::HandleChangeComposition(
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEditor* textEditor = editorBase->AsTextEditor();
|
RefPtr<TextEditor> textEditor = editorBase->AsTextEditor();
|
||||||
return textEditor->OnCompositionChange(*aCompositionChangeEvent);
|
return textEditor->OnCompositionChange(*aCompositionChangeEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1050,7 +1050,7 @@ EditorEventListener::HandleEndComposition(
|
|||||||
MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
|
MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
|
||||||
"eCompositionEnd shouldn't be cancelable");
|
"eCompositionEnd shouldn't be cancelable");
|
||||||
|
|
||||||
TextEditor* textEditor = editorBase->AsTextEditor();
|
RefPtr<TextEditor> textEditor = editorBase->AsTextEditor();
|
||||||
textEditor->OnCompositionEnd(*aCompositionEndEvent);
|
textEditor->OnCompositionEnd(*aCompositionEndEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +63,14 @@ protected:
|
|||||||
|
|
||||||
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
#ifdef HANDLE_NATIVE_TEXT_DIRECTION_SWITCH
|
||||||
nsresult KeyDown(const WidgetKeyboardEvent* aKeyboardEvent);
|
nsresult KeyDown(const WidgetKeyboardEvent* aKeyboardEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult KeyUp(const WidgetKeyboardEvent* aKeyboardEvent);
|
nsresult KeyUp(const WidgetKeyboardEvent* aKeyboardEvent);
|
||||||
#endif
|
#endif
|
||||||
nsresult KeyPress(WidgetKeyboardEvent* aKeyboardEvent);
|
nsresult KeyPress(WidgetKeyboardEvent* aKeyboardEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent);
|
nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent);
|
||||||
nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent);
|
nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent);
|
void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent);
|
||||||
MOZ_CAN_RUN_SCRIPT
|
MOZ_CAN_RUN_SCRIPT
|
||||||
virtual nsresult MouseDown(dom::MouseEvent* aMouseEvent);
|
virtual nsresult MouseDown(dom::MouseEvent* aMouseEvent);
|
||||||
|
@ -5454,10 +5454,10 @@ HTMLEditor::GetPreferredIMEState(IMEState* aState)
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<nsIContent>
|
already_AddRefed<Element>
|
||||||
HTMLEditor::GetInputEventTargetContent()
|
HTMLEditor::GetInputEventTargetElement()
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIContent> target = GetActiveEditingHost();
|
RefPtr<Element> target = GetActiveEditingHost();
|
||||||
return target.forget();
|
return target.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1778,7 +1778,7 @@ protected: // Shouldn't be used by friend classes
|
|||||||
*/
|
*/
|
||||||
already_AddRefed<nsINode> GetFocusedNode();
|
already_AddRefed<nsINode> GetFocusedNode();
|
||||||
|
|
||||||
virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
|
virtual already_AddRefed<Element> GetInputEventTargetElement() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return TRUE if aElement is a table-related elemet and caret was set.
|
* Return TRUE if aElement is a table-related elemet and caret was set.
|
||||||
|
@ -229,6 +229,12 @@ HTMLEditor::InsertTableCellsWithTransaction(int32_t aNumberOfCellsToInsert,
|
|||||||
MOZ_ASSERT_UNREACHABLE("Invalid InsertPosition");
|
MOZ_ASSERT_UNREACHABLE("Invalid InsertPosition");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AutoPlaceholderBatch treateAsOneTransaction(*this);
|
||||||
|
// Prevent auto insertion of BR in new cell until we're done
|
||||||
|
AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
|
||||||
|
*this, EditSubAction::eInsertNode,
|
||||||
|
nsIEditor::eNext);
|
||||||
|
|
||||||
// We control selection resetting after the insert.
|
// We control selection resetting after the insert.
|
||||||
AutoSelectionSetterAfterTableEdit setCaret(*this, table,
|
AutoSelectionSetterAfterTableEdit setCaret(*this, table,
|
||||||
cellDataAtSelection.mCurrent.mRow,
|
cellDataAtSelection.mCurrent.mRow,
|
||||||
|
@ -956,6 +956,7 @@ TextEditRules::WillSetText(bool* aCancel,
|
|||||||
if (!IsPlaintextEditor() ||
|
if (!IsPlaintextEditor() ||
|
||||||
TextEditorRef().IsIMEComposing() ||
|
TextEditorRef().IsIMEComposing() ||
|
||||||
TextEditorRef().IsUndoRedoEnabled() ||
|
TextEditorRef().IsUndoRedoEnabled() ||
|
||||||
|
TextEditorRef().GetEditAction() == EditAction::eReplaceText ||
|
||||||
aMaxLength != -1) {
|
aMaxLength != -1) {
|
||||||
// SetTextImpl only supports plain text editor without IME and
|
// SetTextImpl only supports plain text editor without IME and
|
||||||
// when we don't need to make it undoable.
|
// when we don't need to make it undoable.
|
||||||
|
@ -1460,10 +1460,10 @@ TextEditor::OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent)
|
|||||||
NotifyEditorObservers(eNotifyEditorObserversOfEnd);
|
NotifyEditorObservers(eNotifyEditorObserversOfEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
already_AddRefed<nsIContent>
|
already_AddRefed<Element>
|
||||||
TextEditor::GetInputEventTargetContent()
|
TextEditor::GetInputEventTargetElement()
|
||||||
{
|
{
|
||||||
nsCOMPtr<nsIContent> target = do_QueryInterface(mEventTarget);
|
nsCOMPtr<Element> target = do_QueryInterface(mEventTarget);
|
||||||
return target.forget();
|
return target.forget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,9 @@ public:
|
|||||||
|
|
||||||
// If there are some good name to create non-virtual Undo()/Redo() methods,
|
// If there are some good name to create non-virtual Undo()/Redo() methods,
|
||||||
// we should create them and those methods should just run them.
|
// we should create them and those methods should just run them.
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD Undo(uint32_t aCount) final;
|
NS_IMETHOD Undo(uint32_t aCount) final;
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
NS_IMETHOD Redo(uint32_t aCount) final;
|
NS_IMETHOD Redo(uint32_t aCount) final;
|
||||||
|
|
||||||
NS_IMETHOD Cut() override;
|
NS_IMETHOD Cut() override;
|
||||||
@ -205,6 +207,7 @@ public:
|
|||||||
* @param aCompositionChangeEvent eCompositionChange event which should
|
* @param aCompositionChangeEvent eCompositionChange event which should
|
||||||
* be handled in this editor.
|
* be handled in this editor.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult
|
nsresult
|
||||||
OnCompositionChange(WidgetCompositionEvent& aCompositionChangeEvent);
|
OnCompositionChange(WidgetCompositionEvent& aCompositionChangeEvent);
|
||||||
|
|
||||||
@ -213,6 +216,7 @@ public:
|
|||||||
* event and it's followed by eCompositionEnd event and after
|
* event and it's followed by eCompositionEnd event and after
|
||||||
* OnCompositionChange() is called.
|
* OnCompositionChange() is called.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent);
|
void OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -530,7 +534,7 @@ protected: // Shouldn't be used by friend classes
|
|||||||
*/
|
*/
|
||||||
bool EnsureComposition(WidgetCompositionEvent& aCompositionEvent);
|
bool EnsureComposition(WidgetCompositionEvent& aCompositionEvent);
|
||||||
|
|
||||||
virtual already_AddRefed<nsIContent> GetInputEventTargetContent() override;
|
virtual already_AddRefed<Element> GetInputEventTargetElement() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
mutable nsCOMPtr<nsIDocumentEncoder> mCachedDocumentEncoder;
|
mutable nsCOMPtr<nsIDocumentEncoder> mCachedDocumentEncoder;
|
||||||
|
@ -72,18 +72,39 @@ SimpleTest.waitForFocus(async function() {
|
|||||||
// XXX Perhaps, we need to add border-top-width here if you add new test to have thick border.
|
// XXX Perhaps, we need to add border-top-width here if you add new test to have thick border.
|
||||||
const kPositionerY = -7;
|
const kPositionerY = -7;
|
||||||
|
|
||||||
|
let inputEventExpected = true;
|
||||||
|
function onInput(aEvent) {
|
||||||
|
if (!inputEventExpected) {
|
||||||
|
ok(false, "\"input\" event shouldn't be fired after stopping resizing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface');
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
'"input" event should be never cancelable');
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
'"input" event should always bubble');
|
||||||
|
}
|
||||||
|
|
||||||
|
content.addEventListener("input", onInput);
|
||||||
|
|
||||||
// Click on the positioner.
|
// Click on the positioner.
|
||||||
synthesizeMouse(target, kPositionerX, kPositionerY, {type: "mousedown"});
|
synthesizeMouse(target, kPositionerX, kPositionerY, {type: "mousedown"});
|
||||||
// Drag it delta pixels.
|
// Drag it delta pixels.
|
||||||
synthesizeMouse(target, kPositionerX + aDeltaX, kPositionerY + aDeltaY, {type: "mousemove"});
|
synthesizeMouse(target, kPositionerX + aDeltaX, kPositionerY + aDeltaY, {type: "mousemove"});
|
||||||
// Release the mouse button
|
// Release the mouse button
|
||||||
synthesizeMouse(target, kPositionerX + aDeltaX, kPositionerY + aDeltaY, {type: "mouseup"});
|
synthesizeMouse(target, kPositionerX + aDeltaX, kPositionerY + aDeltaY, {type: "mouseup"});
|
||||||
|
|
||||||
|
inputEventExpected = false;
|
||||||
|
|
||||||
// Move the mouse delta more pixels to the same direction to make sure that the
|
// Move the mouse delta more pixels to the same direction to make sure that the
|
||||||
// positioning operation has stopped.
|
// positioning operation has stopped.
|
||||||
synthesizeMouse(target, kPositionerX + aDeltaX * 2, kPositionerY + aDeltaY * 2, {type: "mousemove"});
|
synthesizeMouse(target, kPositionerX + aDeltaX * 2, kPositionerY + aDeltaY * 2, {type: "mousemove"});
|
||||||
// Click outside of the image to hide the positioner.
|
// Click outside of the image to hide the positioner.
|
||||||
synthesizeMouseAtCenter(outOfEditor, {});
|
synthesizeMouseAtCenter(outOfEditor, {});
|
||||||
|
|
||||||
|
content.removeEventListener("input", onInput);
|
||||||
|
|
||||||
// Get the new dimensions for the absolute positioned element.
|
// Get the new dimensions for the absolute positioned element.
|
||||||
let newRect = target.getBoundingClientRect();
|
let newRect = target.getBoundingClientRect();
|
||||||
isfuzzy(newRect.x, rect.x + aDeltaX, 1, description + "The left should be increased by " + aDeltaX + " pixels");
|
isfuzzy(newRect.x, rect.x + aDeltaX, 1, description + "The left should be increased by " + aDeltaX + " pixels");
|
||||||
|
@ -63,6 +63,8 @@ function runTests() {
|
|||||||
var handler = function(aEvent) {
|
var handler = function(aEvent) {
|
||||||
is(aEvent.target, eventTarget,
|
is(aEvent.target, eventTarget,
|
||||||
"input event is fired on unexpected element: " + aEvent.target.tagName);
|
"input event is fired on unexpected element: " + aEvent.target.tagName);
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
"input event should be dispatched with InputEvent interface");
|
||||||
ok(!aEvent.cancelable, "input event must not be cancelable");
|
ok(!aEvent.cancelable, "input event must not be cancelable");
|
||||||
ok(aEvent.bubbles, "input event must be bubbles");
|
ok(aEvent.bubbles, "input event must be bubbles");
|
||||||
if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
|
if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
|
||||||
|
@ -37,6 +37,8 @@ function runTests() {
|
|||||||
var handler = function(aEvent) {
|
var handler = function(aEvent) {
|
||||||
is(aEvent.target, aElement,
|
is(aEvent.target, aElement,
|
||||||
"input event is fired on unexpected element: " + aEvent.target.tagName);
|
"input event is fired on unexpected element: " + aEvent.target.tagName);
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
"input event should be dispatched with InputEvent interface");
|
||||||
ok(!aEvent.cancelable, "input event must not be cancelable");
|
ok(!aEvent.cancelable, "input event must not be cancelable");
|
||||||
ok(aEvent.bubbles, "input event must be bubbles");
|
ok(aEvent.bubbles, "input event must be bubbles");
|
||||||
if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
|
if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
|
||||||
|
@ -24,6 +24,13 @@ SimpleTest.waitForExplicitFinish();
|
|||||||
var shouldClear = false;
|
var shouldClear = false;
|
||||||
window.addEventListener("dragstart", function(event) { if (shouldClear) event.dataTransfer.clearData(); }, true);
|
window.addEventListener("dragstart", function(event) { if (shouldClear) event.dataTransfer.clearData(); }, true);
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aExpectedTarget, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent, `"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false, `"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true, `"input" event should always bubble ${aDescription}`);
|
||||||
|
is(aEvent.target, aExpectedTarget, `"input" event should be fired on the <${aExpectedTarget.tagName.toLowerCase()}> element ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
function doTest() {
|
function doTest() {
|
||||||
const htmlContextData = { type: "text/_moz_htmlcontext",
|
const htmlContextData = { type: "text/_moz_htmlcontext",
|
||||||
data: "<html><body></body></html>" };
|
data: "<html><body></body></html>" };
|
||||||
@ -39,56 +46,84 @@ function doTest() {
|
|||||||
var input = document.getElementById("input");
|
var input = document.getElementById("input");
|
||||||
var contenteditable = document.getElementById("contenteditable");
|
var contenteditable = document.getElementById("contenteditable");
|
||||||
|
|
||||||
|
var inputEvents = [];
|
||||||
|
function onInput(event) {
|
||||||
|
inputEvents.push(event);
|
||||||
|
}
|
||||||
|
document.addEventListener("input", onInput);
|
||||||
|
|
||||||
var selection = window.getSelection();
|
var selection = window.getSelection();
|
||||||
|
|
||||||
// -------- Test dragging regular text
|
// -------- Test dragging regular text
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
|
inputEvents = [];
|
||||||
var result = synthesizeDragStart(text, [[htmlContextData, htmlInfoData, htmlData,
|
var result = synthesizeDragStart(text, [[htmlContextData, htmlInfoData, htmlData,
|
||||||
{type: "text/plain", data: "Some Text"}]], window, 40, 10);
|
{type: "text/plain", data: "Some Text"}]], window, 40, 10);
|
||||||
is(result, null, "Test dragging regular text");
|
is(result, null, "Test dragging regular text");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dragging from text in <span> element');
|
||||||
|
|
||||||
// -------- Test dragging text from an <input>
|
// -------- Test dragging text from an <input>
|
||||||
input.setSelectionRange(1, 4);
|
input.setSelectionRange(1, 4);
|
||||||
|
inputEvents = [];
|
||||||
result = synthesizeDragStart(input, [[{type: "text/plain", data: "rag"}]], window, 25, 8);
|
result = synthesizeDragStart(input, [[{type: "text/plain", data: "rag"}]], window, 25, 8);
|
||||||
is(result, null, "Test dragging input");
|
is(result, null, "Test dragging input");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dragging from text in <input> element');
|
||||||
|
|
||||||
// -------- Test dragging text from a <textarea>
|
// -------- Test dragging text from a <textarea>
|
||||||
textarea.setSelectionRange(1, 7);
|
textarea.setSelectionRange(1, 7);
|
||||||
|
inputEvents = [];
|
||||||
result = synthesizeDragStart(textarea, [[{type: "text/plain", data: "ome Te"}]], window, 25, 6);
|
result = synthesizeDragStart(textarea, [[{type: "text/plain", data: "ome Te"}]], window, 25, 6);
|
||||||
is(result, null, "Test dragging textarea");
|
is(result, null, "Test dragging textarea");
|
||||||
textarea.blur();
|
textarea.blur();
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dragging from text in <textarea> element');
|
||||||
|
|
||||||
// -------- Test dragging text from a contenteditable
|
// -------- Test dragging text from a contenteditable
|
||||||
selection.selectAllChildren(contenteditable.childNodes[1]);
|
selection.selectAllChildren(contenteditable.childNodes[1]);
|
||||||
|
inputEvents = [];
|
||||||
result = synthesizeDragStart(contenteditable.childNodes[1],
|
result = synthesizeDragStart(contenteditable.childNodes[1],
|
||||||
[[htmlContextDataEditable, htmlInfoData,
|
[[htmlContextDataEditable, htmlInfoData,
|
||||||
{type: "text/html", data: '<b id="bold">editable</b>' },
|
{type: "text/html", data: '<b id="bold">editable</b>' },
|
||||||
{type: "text/plain", data: "editable"}]], window, 5, 6);
|
{type: "text/plain", data: "editable"}]], window, 5, 6);
|
||||||
is(result, null, "Test dragging contenteditable");
|
is(result, null, "Test dragging contenteditable");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dragging from text in contenteditable element');
|
||||||
contenteditable.blur();
|
contenteditable.blur();
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/html to <input>
|
// -------- Test dragging regular text of text/html to <input>
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, input, [], "copy");
|
synthesizeDrop(text, input, [], "copy");
|
||||||
is(input.value, "Some Text", "Drag text/html onto input");
|
is(input.value, "Some Text", "Drag text/html onto input");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dropping text/html into empty <input> element');
|
||||||
|
checkInputEvent(inputEvents[0], input, "when dropping text/html into empty <input> element");
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/html to disabled <input>
|
// -------- Test dragging regular text of text/html to disabled <input>
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
input.disabled = true;
|
input.disabled = true;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, input, [], "copy");
|
synthesizeDrop(text, input, [], "copy");
|
||||||
is(input.value, "", "Drag text/html onto disabled input");
|
is(input.value, "", "Drag text/html onto disabled input");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dropping on disabled <input> element');
|
||||||
input.disabled = false;
|
input.disabled = false;
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/html to readonly <input>
|
// -------- Test dragging regular text of text/html to readonly <input>
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
input.readOnly = true;
|
input.readOnly = true;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, input, [], "copy");
|
synthesizeDrop(text, input, [], "copy");
|
||||||
is(input.value, "", "Drag text/html onto readonly input");
|
is(input.value, "", "Drag text/html onto readonly input");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dropping on readonly <input> element');
|
||||||
input.readOnly = false;
|
input.readOnly = false;
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/html to <input>. This sets
|
// -------- Test dragging regular text of text/html to <input>. This sets
|
||||||
@ -98,16 +133,23 @@ function doTest() {
|
|||||||
shouldClear = true;
|
shouldClear = true;
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"}]], "copy");
|
synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"}]], "copy");
|
||||||
is(input.value, "", "Drag text/html onto input");
|
is(input.value, "", "Drag text/html onto input");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dropping text/html into empty <input> element');
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/plain and text/html to <input>
|
// -------- Test dragging regular text of text/plain and text/html to <input>
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
input.value = "";
|
input.value = "";
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"},
|
synthesizeDrop(text, input, [[{type: "text/html", data: "Some <b>Bold<b> Text"},
|
||||||
{type: "text/plain", data: "Some Plain Text"}]], "copy");
|
{type: "text/plain", data: "Some Plain Text"}]], "copy");
|
||||||
is(input.value, "Some Plain Text", "Drag text/html and text/plain onto input");
|
is(input.value, "Some Plain Text", "Drag text/html and text/plain onto input");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dropping text/plain into empty <input> element');
|
||||||
|
checkInputEvent(inputEvents[0], input, "when dropping text/plain into empty <input> element");
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/plain to <textarea>
|
// -------- Test dragging regular text of text/plain to <textarea>
|
||||||
|
|
||||||
@ -116,44 +158,63 @@ function doTest() {
|
|||||||
// synthesizeDrop(text, textarea, [[{type: "text/plain", data: "Somewhat Longer Text"}]], "copy");
|
// synthesizeDrop(text, textarea, [[{type: "text/plain", data: "Somewhat Longer Text"}]], "copy");
|
||||||
// is(textarea.value, "Somewhat Longer Text", "Drag text/plain onto textarea");
|
// is(textarea.value, "Somewhat Longer Text", "Drag text/plain onto textarea");
|
||||||
|
|
||||||
// -------- Test dragging special text type of text/plain to contenteditable
|
// -------- Test dragging special text type of text/plain to <input>
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, input, [[{type: "text/x-moz-text-internal", data: "Some Special Text"}]], "copy");
|
synthesizeDrop(text, input, [[{type: "text/x-moz-text-internal", data: "Some Special Text"}]], "copy");
|
||||||
is(input.value, "Some Plain Text", "Drag text/x-moz-text-internal onto input");
|
is(input.value, "Some Plain Text", "Drag text/x-moz-text-internal onto input");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when dropping text/x-moz-text-internal into <input> element');
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/plain to contenteditable
|
// -------- Test dragging regular text of text/plain to contenteditable
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, contenteditable, [[{type: "text/plain", data: "Sample Text"}]], "copy");
|
synthesizeDrop(text, contenteditable, [[{type: "text/plain", data: "Sample Text"}]], "copy");
|
||||||
is(contenteditable.childNodes.length, 3, "Drag text/plain onto contenteditable child nodes");
|
is(contenteditable.childNodes.length, 3, "Drag text/plain onto contenteditable child nodes");
|
||||||
is(contenteditable.textContent, "This is some editable text.Sample Text",
|
is(contenteditable.textContent, "This is some editable text.Sample Text",
|
||||||
"Drag text/plain onto contenteditable text");
|
"Drag text/plain onto contenteditable text");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dropping text/plain into contenteditable element');
|
||||||
|
checkInputEvent(inputEvents[0], contenteditable, "when dropping text/plain into contenteditable element");
|
||||||
|
|
||||||
// -------- Test dragging regular text of text/html to contenteditable
|
// -------- Test dragging regular text of text/html to contenteditable
|
||||||
|
|
||||||
selection.selectAllChildren(text);
|
selection.selectAllChildren(text);
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(text, contenteditable, [[{type: "text/html", data: "Sample <i>Italic</i> Text"}]], "copy");
|
synthesizeDrop(text, contenteditable, [[{type: "text/html", data: "Sample <i>Italic</i> Text"}]], "copy");
|
||||||
is(contenteditable.childNodes.length, 6, "Drag text/html onto contenteditable child nodes");
|
is(contenteditable.childNodes.length, 6, "Drag text/html onto contenteditable child nodes");
|
||||||
is(contenteditable.childNodes[4].tagName, "I", "Drag text/html onto contenteditable italic");
|
is(contenteditable.childNodes[4].tagName, "I", "Drag text/html onto contenteditable italic");
|
||||||
is(contenteditable.childNodes[4].textContent, "Italic", "Drag text/html onto contenteditable italic text");
|
is(contenteditable.childNodes[4].textContent, "Italic", "Drag text/html onto contenteditable italic text");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dropping text/html into contenteditable element');
|
||||||
|
checkInputEvent(inputEvents[0], contenteditable, "when dropping text/html into contenteditable element");
|
||||||
|
|
||||||
// -------- Test dragging contenteditable to <input>
|
// -------- Test dragging contenteditable to <input>
|
||||||
|
|
||||||
selection.selectAllChildren(document.getElementById("bold"));
|
selection.selectAllChildren(document.getElementById("bold"));
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(bold, input, [[{type: "text/html", data: "<b>editable</b>"},
|
synthesizeDrop(bold, input, [[{type: "text/html", data: "<b>editable</b>"},
|
||||||
{type: "text/plain", data: "editable"}]], "copy");
|
{type: "text/plain", data: "editable"}]], "copy");
|
||||||
is(input.value, "Some Plain Texteditable", "Move text/html and text/plain from contenteditable onto input");
|
is(input.value, "Some Plain Texteditable", "Copy text/html and text/plain from contenteditable onto input");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dragging from contenteditable to <input> element');
|
||||||
|
checkInputEvent(inputEvents[0], input, "when dragging from contenteditable to <input> element");
|
||||||
|
|
||||||
// -------- Test dragging contenteditable to contenteditable
|
// -------- Test dragging contenteditable to contenteditable
|
||||||
|
|
||||||
shouldClear = false;
|
shouldClear = false;
|
||||||
|
|
||||||
selection.selectAllChildren(contenteditable.childNodes[4]);
|
selection.selectAllChildren(contenteditable.childNodes[4]);
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(contenteditable.childNodes[4], contenteditable, [], "copy");
|
synthesizeDrop(contenteditable.childNodes[4], contenteditable, [], "copy");
|
||||||
is(contenteditable.childNodes.length, 7, "Move text/html and text/plain from contenteditable onto itself child nodes");
|
is(contenteditable.childNodes.length, 7, "Move text/html and text/plain from contenteditable onto itself child nodes");
|
||||||
is(contenteditable.childNodes[6].tagName, "I", "Move text/html and text/plain from contenteditable onto itself italic");
|
is(contenteditable.childNodes[6].tagName, "I", "Move text/html and text/plain from contenteditable onto itself italic");
|
||||||
is(contenteditable.childNodes[6].textContent, "Italic", "Move text/html and text/plain from contenteditable onto itself text");
|
is(contenteditable.childNodes[6].textContent, "Italic", "Move text/html and text/plain from contenteditable onto itself text");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dragging (copy) in a contentediable element');
|
||||||
|
checkInputEvent(inputEvents[0], contenteditable, "when dragging (copy) in a contentediable element");
|
||||||
|
|
||||||
// We'd test 'move' here as well as 'copy', but that requires knowledge of
|
// We'd test 'move' here as well as 'copy', but that requires knowledge of
|
||||||
// the source of the drag which drag simulation doesn't provide.
|
// the source of the drag which drag simulation doesn't provide.
|
||||||
@ -165,9 +226,17 @@ function doTest() {
|
|||||||
|
|
||||||
var nonEditable = document.getElementById("noneditable");
|
var nonEditable = document.getElementById("noneditable");
|
||||||
selection.selectAllChildren(nonEditable);
|
selection.selectAllChildren(nonEditable);
|
||||||
|
inputEvents = [];
|
||||||
synthesizeDrop(nonEditable, document.getElementById("first"), [], "copy");
|
synthesizeDrop(nonEditable, document.getElementById("first"), [], "copy");
|
||||||
is(document.getElementById("nestedce").textContent, " MiddleFirst letter Middle Last part",
|
is(document.getElementById("nestedce").textContent, " MiddleFirst letter Middle Last part",
|
||||||
"Drag non-editable text/html onto contenteditable text");
|
"Drag non-editable text/html onto contenteditable text");
|
||||||
|
todo_is(inputEvents.length, 1,
|
||||||
|
'Only one "input" events should be fired when dragging from inner non-editable element to a contentediable element');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], document.getElementById("nestedce"), "when dragging from inner non-editable element to a contentediable element");
|
||||||
|
}
|
||||||
|
|
||||||
|
document.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
}
|
}
|
||||||
|
@ -76,37 +76,68 @@ async function copyHTMLContent(aInnerHTML) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
async function doTextareaTests(aTextarea) {
|
async function doTextareaTests(aTextarea) {
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
aTextarea.addEventListener("input", onInput);
|
||||||
|
|
||||||
await copyPlaintext("abc\ndef\nghi");
|
await copyPlaintext("abc\ndef\nghi");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
||||||
is(aTextarea.value,
|
is(aTextarea.value,
|
||||||
"> abc\n> def\n> ghi\n\n",
|
"> abc\n> def\n> ghi\n\n",
|
||||||
"Pasted each line should start with \"> \"");
|
"Pasted each line should start with \"> \"");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired #1');
|
||||||
|
checkInputEvent(inputEvents[0], "#1");
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
await copyPlaintext("> abc\n> def\n> ghi");
|
await copyPlaintext("> abc\n> def\n> ghi");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
||||||
is(aTextarea.value,
|
is(aTextarea.value,
|
||||||
">> abc\n>> def\n>> ghi\n\n",
|
">> abc\n>> def\n>> ghi\n\n",
|
||||||
"Pasted each line should be start with \">> \" when already quoted one level");
|
"Pasted each line should be start with \">> \" when already quoted one level");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired #2');
|
||||||
|
checkInputEvent(inputEvents[0], "#2");
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
await copyPlaintext("> abc\n> def\n\nghi");
|
await copyPlaintext("> abc\n> def\n\nghi");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
||||||
is(aTextarea.value,
|
is(aTextarea.value,
|
||||||
">> abc\n>> def\n> \n> ghi\n\n",
|
">> abc\n>> def\n> \n> ghi\n\n",
|
||||||
"Pasted each line should be start with \">> \" when already quoted one level");
|
"Pasted each line should be start with \">> \" when already quoted one level");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired #3');
|
||||||
|
checkInputEvent(inputEvents[0], "#3");
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
await copyPlaintext("abc\ndef\n\n");
|
await copyPlaintext("abc\ndef\n\n");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
synthesizeMouseAtCenter(aTextarea, {button: 1, ctrlKey: true});
|
||||||
is(aTextarea.value,
|
is(aTextarea.value,
|
||||||
"> abc\n> def\n> \n",
|
"> abc\n> def\n> \n",
|
||||||
"If pasted text ends with \"\\n\", only the last line should not started with \">\"");
|
"If pasted text ends with \"\\n\", only the last line should not started with \">\"");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired #4');
|
||||||
|
checkInputEvent(inputEvents[0], "#4");
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
let pasteEventCount = 0;
|
let pasteEventCount = 0;
|
||||||
@ -118,56 +149,81 @@ async function doTextareaTests(aTextarea) {
|
|||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
document.body.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
|
document.body.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
||||||
is(aTextarea.value, "",
|
is(aTextarea.value, "",
|
||||||
"If 'click' event is consumed at capturing phase of the <body>, paste should be canceled");
|
"If 'click' event is consumed at capturing phase of the <body>, paste should be canceled");
|
||||||
is(pasteEventCount, 0,
|
is(pasteEventCount, 0,
|
||||||
"If 'click' event is consumed at capturing phase of the <body>, 'paste' event should not be fired");
|
"If 'click' event is consumed at capturing phase of the <body>, 'paste' event should not be fired");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when the "click" event is canceled');
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
aTextarea.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
|
aTextarea.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
|
||||||
pasteEventCount = 0;
|
pasteEventCount = 0;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
||||||
is(aTextarea.value, "abc",
|
is(aTextarea.value, "abc",
|
||||||
"Even if 'mouseup' event is consumed, paste should be done");
|
"Even if 'mouseup' event is consumed, paste should be done");
|
||||||
is(pasteEventCount, 1,
|
is(pasteEventCount, 1,
|
||||||
"Even if 'mouseup' event is consumed, 'paste' event should be fired once");
|
"Even if 'mouseup' event is consumed, 'paste' event should be fired once");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired even if "mouseup" event is canceled');
|
||||||
|
checkInputEvent(inputEvents[0], 'even if "mouseup" event is canceled');
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
|
aTextarea.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
|
||||||
pasteEventCount = 0;
|
pasteEventCount = 0;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
||||||
is(aTextarea.value, "abc",
|
is(aTextarea.value, "abc",
|
||||||
"Even if 'click' event handler is added to the <textarea>, paste should not be canceled");
|
"Even if 'click' event handler is added to the <textarea>, paste should not be canceled");
|
||||||
is(pasteEventCount, 1,
|
is(pasteEventCount, 1,
|
||||||
"Even if 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
|
"Even if 'click' event handler is added to the <textarea>, 'paste' event should be fired once");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired even if "click" event is canceled in bubbling phase');
|
||||||
|
checkInputEvent(inputEvents[0], 'even if "click" event is canceled in bubbling phase');
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aTextarea.focus();
|
aTextarea.focus();
|
||||||
aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
|
aTextarea.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
|
||||||
pasteEventCount = 0;
|
pasteEventCount = 0;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
synthesizeMouseAtCenter(aTextarea, {button: 1});
|
||||||
todo_is(aTextarea.value, "",
|
todo_is(aTextarea.value, "",
|
||||||
"If 'auxclick' event is consumed, paste should be canceled");
|
"If 'auxclick' event is consumed, paste should be canceled");
|
||||||
todo_is(pasteEventCount, 0,
|
todo_is(pasteEventCount, 0,
|
||||||
"If 'auxclick' event is consumed, 'paste' event should not be fired once");
|
"If 'auxclick' event is consumed, 'paste' event should not be fired once");
|
||||||
|
todo_is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired if "auxclick" event is canceled');
|
||||||
aTextarea.value = "";
|
aTextarea.value = "";
|
||||||
|
|
||||||
aTextarea.removeEventListener("paste", pasteEventLogger);
|
aTextarea.removeEventListener("paste", pasteEventLogger);
|
||||||
|
aTextarea.removeEventListener("input", onInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doContenteditableTests(aEditableDiv) {
|
async function doContenteditableTests(aEditableDiv) {
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
aEditableDiv.addEventListener("input", onInput);
|
||||||
|
|
||||||
await copyPlaintext("abc\ndef\nghi");
|
await copyPlaintext("abc\ndef\nghi");
|
||||||
aEditableDiv.focus();
|
aEditableDiv.focus();
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
|
synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
|
||||||
is(aEditableDiv.innerHTML,
|
is(aEditableDiv.innerHTML,
|
||||||
"<blockquote type=\"cite\">abc<br>def<br>ghi</blockquote>",
|
"<blockquote type=\"cite\">abc<br>def<br>ghi</blockquote>",
|
||||||
"Pasted plaintext should be in <blockquote> element and each linebreaker should be <br> element");
|
"Pasted plaintext should be in <blockquote> element and each linebreaker should be <br> element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired on the editing host');
|
||||||
|
checkInputEvent(inputEvents[0], "(contenteditable)");
|
||||||
aEditableDiv.innerHTML = "";
|
aEditableDiv.innerHTML = "";
|
||||||
|
|
||||||
let pasteEventCount = 0;
|
let pasteEventCount = 0;
|
||||||
@ -179,55 +235,71 @@ async function doContenteditableTests(aEditableDiv) {
|
|||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aEditableDiv.focus();
|
aEditableDiv.focus();
|
||||||
window.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
|
window.addEventListener("click", (event) => { event.preventDefault(); }, {capture: true, once: true});
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
||||||
is(aEditableDiv.innerHTML, "",
|
is(aEditableDiv.innerHTML, "",
|
||||||
"If 'click' event is consumed at capturing phase of the window, paste should be canceled");
|
"If 'click' event is consumed at capturing phase of the window, paste should be canceled");
|
||||||
is(pasteEventCount, 0,
|
is(pasteEventCount, 0,
|
||||||
"If 'click' event is consumed at capturing phase of the window, 'paste' event should be fired once");
|
"If 'click' event is consumed at capturing phase of the window, 'paste' event should be fired once");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when the "click" event is canceled (contenteditable)');
|
||||||
aEditableDiv.innerHTML = "";
|
aEditableDiv.innerHTML = "";
|
||||||
|
|
||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aEditableDiv.focus();
|
aEditableDiv.focus();
|
||||||
aEditableDiv.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
|
aEditableDiv.addEventListener("mouseup", (event) => { event.preventDefault(); }, {once: true});
|
||||||
pasteEventCount = 0;
|
pasteEventCount = 0;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
||||||
is(aEditableDiv.innerHTML, "abc",
|
is(aEditableDiv.innerHTML, "abc",
|
||||||
"Even if 'mouseup' event is consumed, paste should be done");
|
"Even if 'mouseup' event is consumed, paste should be done");
|
||||||
is(pasteEventCount, 1,
|
is(pasteEventCount, 1,
|
||||||
"Even if 'mouseup' event is consumed, 'paste' event should be fired once");
|
"Even if 'mouseup' event is consumed, 'paste' event should be fired once");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired even if "mouseup" event is canceled (contenteditable)');
|
||||||
|
checkInputEvent(inputEvents[0], 'even if "mouseup" event is canceled (contenteditable)');
|
||||||
aEditableDiv.innerHTML = "";
|
aEditableDiv.innerHTML = "";
|
||||||
|
|
||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aEditableDiv.focus();
|
aEditableDiv.focus();
|
||||||
aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
|
aEditableDiv.addEventListener("click", (event) => { event.preventDefault(); }, {once: true});
|
||||||
pasteEventCount = 0;
|
pasteEventCount = 0;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
||||||
is(aEditableDiv.innerHTML, "abc",
|
is(aEditableDiv.innerHTML, "abc",
|
||||||
"Even if 'click' event handler is added to the editing host, paste should not be canceled");
|
"Even if 'click' event handler is added to the editing host, paste should not be canceled");
|
||||||
is(pasteEventCount, 1,
|
is(pasteEventCount, 1,
|
||||||
"Even if 'click' event handler is added to the editing host, 'paste' event should be fired");
|
"Even if 'click' event handler is added to the editing host, 'paste' event should be fired");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired even if "click" event is canceled in bubbling phase (contenteditable)');
|
||||||
|
checkInputEvent(inputEvents[0], 'even if "click" event is canceled in bubbling phase (contenteditable)');
|
||||||
aEditableDiv.innerHTML = "";
|
aEditableDiv.innerHTML = "";
|
||||||
|
|
||||||
await copyPlaintext("abc");
|
await copyPlaintext("abc");
|
||||||
aEditableDiv.focus();
|
aEditableDiv.focus();
|
||||||
aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
|
aEditableDiv.addEventListener("auxclick", (event) => { event.preventDefault(); }, {once: true});
|
||||||
pasteEventCount = 0;
|
pasteEventCount = 0;
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
synthesizeMouseAtCenter(aEditableDiv, {button: 1});
|
||||||
todo_is(aEditableDiv.innerHTML, "",
|
todo_is(aEditableDiv.innerHTML, "",
|
||||||
"If 'auxclick' event is consumed, paste should be canceled");
|
"If 'auxclick' event is consumed, paste should be canceled");
|
||||||
todo_is(pasteEventCount, 0,
|
todo_is(pasteEventCount, 0,
|
||||||
"If 'auxclick' event is consumed, 'paste' event should not be fired");
|
"If 'auxclick' event is consumed, 'paste' event should not be fired");
|
||||||
|
todo_is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired if "auxclick" event is canceled (contenteditable)');
|
||||||
aEditableDiv.innerHTML = "";
|
aEditableDiv.innerHTML = "";
|
||||||
|
|
||||||
aEditableDiv.removeEventListener("paste", pasteEventLogger);
|
aEditableDiv.removeEventListener("paste", pasteEventLogger);
|
||||||
|
|
||||||
// Oddly, copyHTMLContent fails randomly only on Linux. Let's skip this.
|
// Oddly, copyHTMLContent fails randomly only on Linux. Let's skip this.
|
||||||
if (navigator.platform.startsWith("Linux")) {
|
if (navigator.platform.startsWith("Linux")) {
|
||||||
|
aEditableDiv.removeEventListener("input", onInput);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await copyHTMLContent("<p>abc</p><p>def</p><p>ghi</p>");
|
await copyHTMLContent("<p>abc</p><p>def</p><p>ghi</p>");
|
||||||
aEditableDiv.focus();
|
aEditableDiv.focus();
|
||||||
|
inputEvents = [];
|
||||||
synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
|
synthesizeMouseAtCenter(aEditableDiv, {button: 1, ctrlKey: true});
|
||||||
if (!navigator.appVersion.includes("Android")) {
|
if (!navigator.appVersion.includes("Android")) {
|
||||||
is(aEditableDiv.innerHTML,
|
is(aEditableDiv.innerHTML,
|
||||||
@ -239,7 +311,12 @@ async function doContenteditableTests(aEditableDiv) {
|
|||||||
"<blockquote type=\"cite\">abc<br><br>def<br><br>ghi</blockquote>",
|
"<blockquote type=\"cite\">abc<br><br>def<br><br>ghi</blockquote>",
|
||||||
"Pasted HTML content should be set to the <blockquote>");
|
"Pasted HTML content should be set to the <blockquote>");
|
||||||
}
|
}
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired when pasting HTML');
|
||||||
|
checkInputEvent(inputEvents[0], "when pasting HTML");
|
||||||
aEditableDiv.innerHTML = "";
|
aEditableDiv.innerHTML = "";
|
||||||
|
|
||||||
|
aEditableDiv.removeEventListener("input", onInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doNestedEditorTests(aEditableDiv) {
|
async function doNestedEditorTests(aEditableDiv) {
|
||||||
|
@ -19,6 +19,12 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
editor.focus();
|
editor.focus();
|
||||||
selection.collapse(editor, 0);
|
selection.collapse(editor, 0);
|
||||||
|
|
||||||
@ -26,6 +32,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
|
|
||||||
getEditor().flags |= SpecialPowers.Ci.nsIPlaintextEditor.eEditorPlaintextMask;
|
getEditor().flags |= SpecialPowers.Ci.nsIPlaintextEditor.eEditorPlaintextMask;
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
getEditorMailSupport().insertAsCitedQuotation("this is quoted text\nAnd here is second line.", "this is cited text", false);
|
getEditorMailSupport().insertAsCitedQuotation("this is quoted text\nAnd here is second line.", "this is cited text", false);
|
||||||
|
|
||||||
ok(selection.isCollapsed,
|
ok(selection.isCollapsed,
|
||||||
@ -36,12 +43,21 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor");
|
||||||
is(editor.innerHTML, '<span style="white-space: pre-wrap;">> this is quoted text<br>> And here is second line.<br><br></span>',
|
is(editor.innerHTML, '<span style="white-space: pre-wrap;">> this is quoted text<br>> And here is second line.<br><br></span>',
|
||||||
"The quoted text should be inserted as plaintext into the plaintext editor");
|
"The quoted text should be inserted as plaintext into the plaintext editor");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of plaintext editor');
|
||||||
|
|
||||||
// Tests when the editor is in HTML editor mode.
|
// Tests when the editor is in HTML editor mode.
|
||||||
getEditor().flags &= ~SpecialPowers.Ci.nsIPlaintextEditor.eEditorPlaintextMask;
|
getEditor().flags &= ~SpecialPowers.Ci.nsIPlaintextEditor.eEditorPlaintextMask;
|
||||||
|
|
||||||
editor.innerHTML = "";
|
editor.innerHTML = "";
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>", "this is cited text", false);
|
getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>", "this is cited text", false);
|
||||||
|
|
||||||
ok(selection.isCollapsed,
|
ok(selection.isCollapsed,
|
||||||
@ -52,9 +68,18 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)");
|
||||||
is(editor.innerHTML,
|
is(editor.innerHTML,
|
||||||
'<blockquote type="cite" cite="this is cited text">this is quoted text<br></blockquote>', "The quoted text should be inserted as plaintext into the HTML editor");
|
'<blockquote type="cite" cite="this is cited text">this is quoted text<br></blockquote>', "The quoted text should be inserted as plaintext into the HTML editor");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as plaintext)');
|
||||||
|
|
||||||
editor.innerHTML = "";
|
editor.innerHTML = "";
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>And here is second line.", "this is cited text", true);
|
getEditorMailSupport().insertAsCitedQuotation("this is quoted text<br>And here is second line.", "this is cited text", true);
|
||||||
|
|
||||||
ok(selection.isCollapsed,
|
ok(selection.isCollapsed,
|
||||||
@ -65,6 +90,14 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
"focus offset of Selection should be next to inserted <span> element after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)");
|
||||||
is(editor.innerHTML, '<blockquote type="cite" cite="this is cited text">this is quoted text<br>And here is second line.</blockquote>',
|
is(editor.innerHTML, '<blockquote type="cite" cite="this is cited text">this is quoted text<br>And here is second line.</blockquote>',
|
||||||
"The quoted text should be inserted as HTML source into the HTML editor");
|
"The quoted text should be inserted as HTML source into the HTML editor");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'One "input" event should be fired on the editing host after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble after calling nsIEditorMailSupport.insertAsCitedQuotation() of HTMLEditor editor (inserting as HTML source)');
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -22,31 +22,37 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let textarea = document.getElementsByTagName("textarea")[0];
|
let textarea = document.getElementsByTagName("textarea")[0];
|
||||||
let contenteditable = document.getElementById("content");
|
let contenteditable = document.getElementById("content");
|
||||||
let selection = window.getSelection();
|
let selection = window.getSelection();
|
||||||
let inputEventCount = 0;
|
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
function onInput(event) {
|
function onInput(event) {
|
||||||
inputEventCount++;
|
inputEvents.push(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
input.focus();
|
input.focus();
|
||||||
input.selectionStart = input.selectionEnd = 3;
|
input.selectionStart = input.selectionEnd = 3;
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
input.addEventListener("input", onInput);
|
input.addEventListener("input", onInput);
|
||||||
try {
|
try {
|
||||||
getPlaintextEditor(input).insertLineBreak();
|
getPlaintextEditor(input).insertLineBreak();
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
input.removeEventListener("input", onInput);
|
input.removeEventListener("input", onInput);
|
||||||
is(input.value, "abcdef", "nsIPlaintextEditor.insertLineBreak() should do nothing on single line editor");
|
is(input.value, "abcdef", "nsIPlaintextEditor.insertLineBreak() should do nothing on single line editor");
|
||||||
is(inputEventCount, 0, "nsIPlaintextEditor.insertLineBreak() shouldn't cause 'input' event on single line editor");
|
is(inputEvents.length, 0, "nsIPlaintextEditor.insertLineBreak() shouldn't cause 'input' event on single line editor");
|
||||||
|
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
textarea.selectionStart = textarea.selectionEnd = 3;
|
textarea.selectionStart = textarea.selectionEnd = 3;
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
textarea.addEventListener("input", onInput);
|
textarea.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(textarea).insertLineBreak();
|
getPlaintextEditor(textarea).insertLineBreak();
|
||||||
textarea.removeEventListener("input", onInput);
|
textarea.removeEventListener("input", onInput);
|
||||||
is(textarea.value, "abc\ndef", "nsIPlaintextEditor.insertLineBreak() should insert \n into multi-line editor");
|
is(textarea.value, "abc\ndef", "nsIPlaintextEditor.insertLineBreak() should insert \n into multi-line editor");
|
||||||
is(inputEventCount, 1, "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on multi-line editor");
|
is(inputEvents.length, 1, "nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on multi-line editor");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (on multi-line editor)');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (on multi-line editor)');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (on multi-line editor)');
|
||||||
|
|
||||||
// Note that despite of the name, insertLineBreak() should insert paragraph separator in HTMLEditor.
|
// Note that despite of the name, insertLineBreak() should insert paragraph separator in HTMLEditor.
|
||||||
|
|
||||||
@ -56,53 +62,77 @@ SimpleTest.waitForFocus(function() {
|
|||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild, 3);
|
selection.collapse(contenteditable.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "abc<br>def",
|
is(contenteditable.innerHTML, "abc<br>def",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into text node when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into text node when defaultParagraphSeparator is \"br\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"br\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #1');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #1');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "br") #1');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<p>abcdef</p>";
|
contenteditable.innerHTML = "<p>abcdef</p>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should add <p> element after <p> element even when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should add <p> element after <p> element even when defaultParagraphSeparator is \"br\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"br\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #2');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #2');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "br") #2');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<div>abcdef</div>";
|
contenteditable.innerHTML = "<div>abcdef</div>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<div>abc<br>def</div>",
|
is(contenteditable.innerHTML, "<div>abc<br>def</div>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <div> element when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <div> element when defaultParagraphSeparator is \"br\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"br\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #3');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #3');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "br") #3');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<pre>abc<br>def</pre>",
|
is(contenteditable.innerHTML, "<pre>abc<br>def</pre>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <pre> element when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <pre> element when defaultParagraphSeparator is \"br\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"br\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"br\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "br") #4');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "br") #4');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "br") #4');
|
||||||
|
|
||||||
document.execCommand("defaultParagraphSeparator", false, "p");
|
document.execCommand("defaultParagraphSeparator", false, "p");
|
||||||
|
|
||||||
@ -110,53 +140,77 @@ SimpleTest.waitForFocus(function() {
|
|||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild, 3);
|
selection.collapse(contenteditable.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should create <p> elements when there is only text node and defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should create <p> elements when there is only text node and defaultParagraphSeparator is \"p\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"p\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #1');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #1');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "p") #1');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<p>abcdef</p>";
|
contenteditable.innerHTML = "<p>abcdef</p>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should add <p> element after <p> element when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should add <p> element after <p> element when defaultParagraphSeparator is \"p\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"p\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #2');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #2');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "p") #2');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<div>abcdef</div>";
|
contenteditable.innerHTML = "<div>abcdef</div>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<div>abc</div><div>def</div>",
|
is(contenteditable.innerHTML, "<div>abc</div><div>def</div>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should add <div> element after <div> element even when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should add <div> element after <div> element even when defaultParagraphSeparator is \"p\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"p\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #3');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #3');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "p") #3');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<pre>abc<br>def</pre>",
|
is(contenteditable.innerHTML, "<pre>abc<br>def</pre>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <pre> element when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <pre> element when defaultParagraphSeparator is \"p\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"p\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"p\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "p") #4');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "p") #4');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "p") #4');
|
||||||
|
|
||||||
document.execCommand("defaultParagraphSeparator", false, "div");
|
document.execCommand("defaultParagraphSeparator", false, "div");
|
||||||
|
|
||||||
@ -164,53 +218,77 @@ SimpleTest.waitForFocus(function() {
|
|||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild, 3);
|
selection.collapse(contenteditable.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<div>abc</div><div>def</div>",
|
is(contenteditable.innerHTML, "<div>abc</div><div>def</div>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should create <div> elements when there is only text node and defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should create <div> elements when there is only text node and defaultParagraphSeparator is \"div\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has only text node when defaultParagraphSeparator is \"div\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #1');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #1');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "div") #1');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<p>abcdef</p>";
|
contenteditable.innerHTML = "<p>abcdef</p>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
is(contenteditable.innerHTML, "<p>abc</p><p>def</p>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should add <p> element after <p> element even when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should add <p> element after <p> element even when defaultParagraphSeparator is \"div\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <p> element when defaultParagraphSeparator is \"div\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #2');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #2');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "div") #2');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<div>abcdef</div>";
|
contenteditable.innerHTML = "<div>abcdef</div>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<div>abc</div><div>def</div>",
|
is(contenteditable.innerHTML, "<div>abc</div><div>def</div>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should add <div> element after <div> element when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should add <div> element after <div> element when defaultParagraphSeparator is \"div\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <div> element when defaultParagraphSeparator is \"div\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #3');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #3');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "div") #3');
|
||||||
|
|
||||||
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
contenteditable.innerHTML = "<pre>abcdef</pre>";
|
||||||
contenteditable.focus();
|
contenteditable.focus();
|
||||||
contenteditable.scrollTop;
|
contenteditable.scrollTop;
|
||||||
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
selection.collapse(contenteditable.firstChild.firstChild, 3);
|
||||||
inputEventCount = 0;
|
inputEvents = [];
|
||||||
contenteditable.addEventListener("input", onInput);
|
contenteditable.addEventListener("input", onInput);
|
||||||
getPlaintextEditor(contenteditable).insertLineBreak();
|
getPlaintextEditor(contenteditable).insertLineBreak();
|
||||||
contenteditable.removeEventListener("input", onInput);
|
contenteditable.removeEventListener("input", onInput);
|
||||||
is(contenteditable.innerHTML, "<pre>abc<br>def</pre>",
|
is(contenteditable.innerHTML, "<pre>abc<br>def</pre>",
|
||||||
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <pre> element when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should insert <br> element into <pre> element when defaultParagraphSeparator is \"div\"");
|
||||||
is(inputEventCount, 1,
|
is(inputEvents.length, 1,
|
||||||
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"div\"");
|
"nsIPlaintextEditor.insertLineBreak() should cause 'input' event once on contenteditable which has <pre> element when defaultParagraphSeparator is \"div\"");
|
||||||
|
ok(inputEvents[0] instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface (when defaultParagraphSeparator is "div") #4');
|
||||||
|
is(inputEvents[0].cancelable, false,
|
||||||
|
'"input" event should be never cancelable even if "click" event (when defaultParagraphSeparator is "div") #4');
|
||||||
|
is(inputEvents[0].bubbles, true,
|
||||||
|
'"input" event should always bubble (when defaultParagraphSeparator is "div") #4');
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -19,17 +19,38 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
selection.collapse(editor.firstChild, 0);
|
selection.collapse(editor.firstChild, 0);
|
||||||
getTableEditor().deleteTableCell(1);
|
getTableEditor().deleteTableCell(1);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableCell(1) should do nothing if selection is not in <table>");
|
"nsITableEditor.deleteTableCell(1) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.deleteTableCell(1) does nothing');
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().deleteTableCell(1);
|
getTableEditor().deleteTableCell(1);
|
||||||
ok(false, "getTableEditor().deleteTableCell(1) without selection ranges should throw exception");
|
ok(false, "getTableEditor().deleteTableCell(1) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().deleteTableCell(1) without selection ranges should throw exception");
|
ok(true, "getTableEditor().deleteTableCell(1) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.deleteTableCell(1) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -37,6 +58,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select">cell1-1</td><td>cell1-2</td><td>cell1-3</td></tr>' +
|
'<tr><td id="select">cell1-1</td><td>cell1-2</td><td>cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
let range = document.createRange();
|
let range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -46,12 +68,16 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove only selected cell when only one cell is selected #1-1");
|
"nsITableEditor.deleteTableCellContents(1) should remove only selected cell when only one cell is selected #1-1");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when only one cell is selected #1-1');
|
||||||
|
checkInputEvent(inputEvents[0], "when only one cell is selected #1-1");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td>cell1-1</td><td id="select">cell1-2</td><td>cell1-3</td></tr>' +
|
'<tr><td>cell1-1</td><td id="select">cell1-2</td><td>cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -61,12 +87,16 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove only selected cell when only one cell is selected #1-2");
|
"nsITableEditor.deleteTableCellContents(1) should remove only selected cell when only one cell is selected #1-2");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when only one cell is selected #1-2');
|
||||||
|
checkInputEvent(inputEvents[0], "when only one cell is selected #1-2");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td>cell1-1</td><td>cell1-2</td><td id="select">cell1-3</td></tr>' +
|
'<tr><td>cell1-1</td><td>cell1-2</td><td id="select">cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -76,6 +106,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove only selected cell when only one cell is selected #1-3");
|
"nsITableEditor.deleteTableCellContents(1) should remove only selected cell when only one cell is selected #1-3");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when only one cell is selected #1-3');
|
||||||
|
checkInputEvent(inputEvents[0], "when only one cell is selected #1-3");
|
||||||
|
|
||||||
// When only one cell element is selected, the argument should be used.
|
// When only one cell element is selected, the argument should be used.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -83,6 +116,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select">cell1-1</td><td>cell1-2</td><td>cell1-3</td></tr>' +
|
'<tr><td id="select">cell1-1</td><td>cell1-2</td><td>cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -91,7 +125,10 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell1-3</td></tr>" +
|
"<tr><td>cell1-3</td></tr>" +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(2) should remove selected cell element and next cell element in same row");
|
"nsITableEditor.deleteTableCellContents(2) should remove selected cell element and next cell element in same row #1-4");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when only one cell is selected #1-4');
|
||||||
|
checkInputEvent(inputEvents[0], "when only one cell is selected #1-4");
|
||||||
|
|
||||||
// When the argument is larger than remaining cell elements from selected
|
// When the argument is larger than remaining cell elements from selected
|
||||||
// cell element, the behavior is really buggy.
|
// cell element, the behavior is really buggy.
|
||||||
@ -100,6 +137,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td>cell1-1</td><td>cell1-2</td><td id="select">cell1-3</td></tr>' +
|
'<tr><td>cell1-1</td><td>cell1-2</td><td id="select">cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -109,6 +147,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(2) should remove selected cell element and its previous cell element when it reaches the last cell element in the row");
|
"nsITableEditor.deleteTableCellContents(2) should remove selected cell element and its previous cell element when it reaches the last cell element in the row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when the argument is larger than remaining cell elements from selected cell element');
|
||||||
|
checkInputEvent(inputEvents[0], "when the argument is larger than remaining cell elements from selected cell element");
|
||||||
|
|
||||||
// XXX If the former case is expected, first row should be removed in this
|
// XXX If the former case is expected, first row should be removed in this
|
||||||
// case, but it removes only selected cell and its previous cell.
|
// case, but it removes only selected cell and its previous cell.
|
||||||
@ -117,6 +158,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td>cell1-1</td><td>cell1-2</td><td id="select">cell1-3</td></tr>' +
|
'<tr><td>cell1-1</td><td>cell1-2</td><td id="select">cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -125,6 +167,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(4) should remove the first row when a cell in it is selected and the argument is larger than number of cells in the row");
|
"nsITableEditor.deleteTableCellContents(4) should remove the first row when a cell in it is selected and the argument is larger than number of cells in the row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when the argument is larger than number of cells in the row');
|
||||||
|
checkInputEvent(inputEvents[0], "when the argument is larger than number of cells in the row");
|
||||||
|
|
||||||
// If 2 or more cells are selected, the argument should be ignored.
|
// If 2 or more cells are selected, the argument should be ignored.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -132,6 +177,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select1">cell1-1</td><td>cell1-2</td><td>cell1-3</td></tr>' +
|
'<tr><td id="select1">cell1-1</td><td>cell1-2</td><td>cell1-3</td></tr>' +
|
||||||
'<tr><td>cell2-1</td><td id="select2">cell2-2</td><td>cell2-3</td></tr>' +
|
'<tr><td>cell2-1</td><td id="select2">cell2-2</td><td>cell2-3</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -144,6 +190,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove selected cell elements even if the argument is smaller than number of selected cells");
|
"nsITableEditor.deleteTableCellContents(1) should remove selected cell elements even if the argument is smaller than number of selected cells");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired even if the argument is smaller than number of selected cells');
|
||||||
|
checkInputEvent(inputEvents[0], "even if the argument is smaller than number of selected cells");
|
||||||
|
|
||||||
// If all cells in a row are selected, the <tr> element should also be removed.
|
// If all cells in a row are selected, the <tr> element should also be removed.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -151,6 +200,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td><td id="select3">cell1-3</td></tr>' +
|
'<tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td><td id="select3">cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -165,12 +215,16 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove also <tr> element when all cell elements in a row is selected");
|
"nsITableEditor.deleteTableCellContents(1) should remove also <tr> element when all cell elements in a row is selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all cell elements in a row is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when all cell elements in a row is selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td id="select">cell1-1</td></tr>' +
|
'<tr><td id="select">cell1-1</td></tr>' +
|
||||||
"<tr><td>cell2-1</td></tr>" +
|
"<tr><td>cell2-1</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -179,6 +233,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td></tr>" +
|
"<tr><td>cell2-1</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove also <tr> element when a cell element which is only child of a row is selected");
|
"nsITableEditor.deleteTableCellContents(1) should remove also <tr> element when a cell element which is only child of a row is selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell element which is only child of a row is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell element which is only child of a row is selected");
|
||||||
|
|
||||||
// If all cells are removed, the <table> element should be removed.
|
// If all cells are removed, the <table> element should be removed.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -186,6 +243,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td></tr>' +
|
'<tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td></tr>' +
|
||||||
'<tr><td id="select3">cell2-1</td><td id="select4">cell2-2</td></tr>' +
|
'<tr><td id="select3">cell2-1</td><td id="select4">cell2-2</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -201,17 +259,24 @@ SimpleTest.waitForFocus(function() {
|
|||||||
getTableEditor().deleteTableCell(1);
|
getTableEditor().deleteTableCell(1);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove also <table> element when all cell elements are selected");
|
"nsITableEditor.deleteTableCellContents(1) should remove also <table> element when all cell elements are selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all cell elements are selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when all cell elements are selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td id="select">cell1-1</td></tr>' +
|
'<tr><td id="select">cell1-1</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableCell(1);
|
getTableEditor().deleteTableCell(1);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove also <table> element when a cell element which is only child of <table> is selected");
|
"nsITableEditor.deleteTableCellContents(1) should remove also <table> element when a cell element which is only child of <table> is selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell element which is only child of <table> is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell element which is only child of <table> is selected");
|
||||||
|
|
||||||
// rowspan
|
// rowspan
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -220,6 +285,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-2</td></tr>" +
|
||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -230,6 +296,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning rows");
|
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when removing the cell spanning rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when removing the cell spanning rows");
|
||||||
|
|
||||||
// XXX cell3-1 is also removed even though it's not selected.
|
// XXX cell3-1 is also removed even though it's not selected.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -238,6 +307,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-2</td></tr>" +
|
||||||
'<tr><td>cell3-1</td><td id="select2">cell3-2</td></tr>' +
|
'<tr><td>cell3-1</td><td id="select2">cell3-2</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -251,6 +321,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td></tr>" +
|
"<tr><td>cell3-1</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning rows (when 2 cell elements are selected)");
|
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning rows (when 2 cell elements are selected)");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when removing the cell spanning rows (when 2 cell elements are selected)');
|
||||||
|
checkInputEvent(inputEvents[0], "when removing the cell spanning rows (when 2 cell elements are selected)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -258,6 +331,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select">cell2-2</td></tr>' +
|
'<tr><td id="select">cell2-2</td></tr>' +
|
||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -267,6 +341,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element is removed");
|
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element is removed");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
"Only one \"input\" event should be fired when spanned <tr>'s last child cell element is removed");
|
||||||
|
checkInputEvent(inputEvents[0], "when spanned <tr>'s last child cell element is removed");
|
||||||
|
|
||||||
// XXX broken case, the second row isn't removed.
|
// XXX broken case, the second row isn't removed.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -275,6 +352,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select1">cell2-2</td></tr>' +
|
'<tr><td id="select1">cell2-2</td></tr>' +
|
||||||
'<tr><td>cell3-1</td><td id="select2">cell3-2</td></tr>' +
|
'<tr><td>cell3-1</td><td id="select2">cell3-2</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -287,6 +365,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td></tr>" +
|
"<tr><td>cell3-1</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element is removed (when 2 cell elements are selected)");
|
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element is removed (when 2 cell elements are selected)");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
"Only one \"input\" event should be fired when spanned <tr>'s last child cell element is removed (when 2 cell elements are selected)");
|
||||||
|
checkInputEvent(inputEvents[0], "when spanned <tr>'s last child cell element is removed (when 2 cell elements are selected)");
|
||||||
|
|
||||||
// XXX broken case, neither the selected cell nor the second row is removed.
|
// XXX broken case, neither the selected cell nor the second row is removed.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -295,6 +376,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select">cell2-2</td></tr>' +
|
'<tr><td id="select">cell2-2</td></tr>' +
|
||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -304,6 +386,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element is removed (removing middle row)");
|
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element is removed (removing middle row)");
|
||||||
|
todo_is(inputEvents.length, 1,
|
||||||
|
"Only one \"input\" event should be fired when spanned <tr>'s last child cell element is removed (removing middle row)");
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when spanned <tr>'s last child cell element is removed (removing middle row)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -311,6 +398,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select1">cell2-2</td></tr>' +
|
'<tr><td id="select1">cell2-2</td></tr>' +
|
||||||
'<tr><td>cell3-1</td><td id="select2">cell3-2</td></tr>' +
|
'<tr><td>cell3-1</td><td id="select2">cell3-2</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -322,6 +410,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td rowspan="2">cell1-1</td><td>cell1-2</td></tr>' +
|
'<tr><td rowspan="2">cell1-1</td><td>cell1-2</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element and remove the last row");
|
"nsITableEditor.deleteTableCellContents(1) should adjust rowspan when spanned <tr>'s last child cell element and remove the last row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
"Only one \"input\" event should be fired when spanned <tr>'s last child cell element and remove the last row");
|
||||||
|
checkInputEvent(inputEvents[0], "when spanned <tr>'s last child cell element and remove the last row");
|
||||||
|
|
||||||
// colspan
|
// colspan
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -329,6 +420,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select" colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
'<tr><td id="select" colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -338,12 +430,16 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning columns");
|
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning columns");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when removing the cell spanning columns');
|
||||||
|
checkInputEvent(inputEvents[0], "when removing the cell spanning columns");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td id="select1" colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
'<tr><td id="select1" colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
||||||
'<tr><td>cell2-1</td><td id="select2">cell2-2</td><td>cell2-3</td></tr>' +
|
'<tr><td>cell2-1</td><td id="select2">cell2-2</td><td>cell2-3</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -356,6 +452,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning columns and the other selected cell element");
|
"nsITableEditor.deleteTableCellContents(1) should just remove the cell spanning columns and the other selected cell element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when removing the cell spanning columns and the other selected cell element');
|
||||||
|
checkInputEvent(inputEvents[0], "when removing the cell spanning columns and the other selected cell element");
|
||||||
|
|
||||||
// XXX broken case, colspan is not adjusted.
|
// XXX broken case, colspan is not adjusted.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -363,6 +462,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
'<tr><td colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
||||||
'<tr><td>cell2-1</td><td id="select">cell2-2</td><td>cell2-3</td></tr>' +
|
'<tr><td>cell2-1</td><td id="select">cell2-2</td><td>cell2-3</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -372,12 +472,16 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust different row's colspan when corresponding cell element is removed");
|
"nsITableEditor.deleteTableCellContents(1) should adjust different row's colspan when corresponding cell element is removed");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when corresponding cell element is removed');
|
||||||
|
checkInputEvent(inputEvents[0], "when corresponding cell element is removed");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
'<tr><td colspan="2">cell1-1</td><td>cell1-3</td></tr>' +
|
||||||
'<tr><td>cell2-1</td><td id="select1">cell2-2</td><td id="select2">cell2-3</td></tr>' +
|
'<tr><td>cell2-1</td><td id="select1">cell2-2</td><td id="select2">cell2-3</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -390,12 +494,16 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td></tr>" +
|
"<tr><td>cell2-1</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust different row's colspan when corresponding cell element is removed (when 2 cell elements are selected)");
|
"nsITableEditor.deleteTableCellContents(1) should adjust different row's colspan when corresponding cell element is removed (when 2 cell elements are selected)");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when corresponding cell element is removed (when 2 cell elements are selected)');
|
||||||
|
checkInputEvent(inputEvents[0], "when corresponding cell element is removed (when 2 cell elements are selected)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
'<tr><td>cell1-1</td><td id="select1">cell1-2</td><td>cell1-4</td></tr>' +
|
'<tr><td>cell1-1</td><td id="select1">cell1-2</td><td>cell1-4</td></tr>' +
|
||||||
'<tr><td id="select2" colspan="2">cell2-1</td><td>cell2-3</td><td>cell2-4</td></tr>' +
|
'<tr><td id="select2" colspan="2">cell2-1</td><td>cell2-3</td><td>cell2-4</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -408,6 +516,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-3</td><td>cell2-4</td></tr>" +
|
"<tr><td>cell2-3</td><td>cell2-4</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should adjust different row's colspan when corresponding cell element is removed (when 2 cell elements are selected)");
|
"nsITableEditor.deleteTableCellContents(1) should adjust different row's colspan when corresponding cell element is removed (when 2 cell elements are selected)");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when corresponding cell element is removed (when 2 cell elements are selected)');
|
||||||
|
checkInputEvent(inputEvents[0], "when corresponding cell element is removed (when 2 cell elements are selected)");
|
||||||
|
|
||||||
// When a cell contains first selection range, it should be removed.
|
// When a cell contains first selection range, it should be removed.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -416,6 +527,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.scrollTop; // requires layout information.
|
editor.scrollTop; // requires layout information.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().deleteTableCell(1);
|
getTableEditor().deleteTableCell(1);
|
||||||
@ -424,6 +536,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(1) should remove only a cell containing first selection range when there is no selected cell element");
|
"nsITableEditor.deleteTableCellContents(1) should remove only a cell containing first selection range when there is no selected cell element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when there is no selected cell element');
|
||||||
|
checkInputEvent(inputEvents[0], "when there is no selected cell element");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -431,6 +546,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.scrollTop; // requires layout information.
|
editor.scrollTop; // requires layout information.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().deleteTableCell(2);
|
getTableEditor().deleteTableCell(2);
|
||||||
@ -439,6 +555,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td><td>cell2-3</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents(2) should remove only 2 cell elements starting from a cell containing first selection range when there is no selected cell element");
|
"nsITableEditor.deleteTableCellContents(2) should remove only 2 cell elements starting from a cell containing first selection range when there is no selected cell element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when there is no selected cell element');
|
||||||
|
checkInputEvent(inputEvents[0], "when there is no selected cell element");
|
||||||
|
|
||||||
|
editor.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -19,17 +19,38 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
selection.collapse(editor.firstChild, 0);
|
selection.collapse(editor.firstChild, 0);
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().deleteTableCellContents();
|
getTableEditor().deleteTableCellContents();
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableColumn() should do nothing if selection is not in <table>");
|
"nsITableEditor.deleteTableCellContents() should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.deleteTableCellContents() does nothing');
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().deleteTableCellContents();
|
getTableEditor().deleteTableCellContents();
|
||||||
ok(false, "getTableEditor().deleteTableCellContents() without selection ranges should throw exception");
|
ok(false, "getTableEditor().deleteTableCellContents() without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().deleteTableCellContents() without selection ranges should throw exception");
|
ok(true, "getTableEditor().deleteTableCellContents() without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.deleteTableCellContents() causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -38,6 +59,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
inputEvents = [];
|
||||||
let range = document.createRange();
|
let range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -47,6 +69,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace the selected cell's text with <br> element");
|
"nsITableEditor.deleteTableCellContents() should replace the selected cell's text with <br> element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all text in a cell is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when all text in a cell is selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -54,6 +79,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -63,6 +89,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace the selected cell's <ul> element with <br> element");
|
"nsITableEditor.deleteTableCellContents() should replace the selected cell's <ul> element with <br> element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when <ul> element in a cell is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when <ul> element in a cell is selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -70,6 +99,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select2">cell2-1</td><td>cell2-2</td></tr>' +
|
'<tr><td id="select2">cell2-1</td><td>cell2-2</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -82,6 +112,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select2"><br></td><td>cell2-2</td></tr>' +
|
'<tr><td id="select2"><br></td><td>cell2-2</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace the selected 2 cells' text with <br> element");
|
"nsITableEditor.deleteTableCellContents() should replace the selected 2 cells' text with <br> element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when 2 cell elements are selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when 2 cell elements are selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -89,6 +122,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select3">cell2-1</td><td id="select4">cell2-2</td></tr>' +
|
'<tr><td id="select3">cell2-1</td><td id="select4">cell2-2</td></tr>' +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -107,6 +141,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select3"><br></td><td id="select4"><br></td></tr>' +
|
'<tr><td id="select3"><br></td><td id="select4"><br></td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace the selected 4 cells' text with <br> element");
|
"nsITableEditor.deleteTableCellContents() should replace the selected 4 cells' text with <br> element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when 4 cell elements are selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when 4 cell elements are selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -114,6 +151,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -123,6 +161,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace the selected cell's text with <br> element (even if the cell is row-spanning)");
|
"nsITableEditor.deleteTableCellContents() should replace the selected cell's text with <br> element (even if the cell is row-spanning)");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell element are selected (even if the cell is row-spanning)');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell element are selected (even if the cell is row-spanning)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -130,6 +171,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -139,6 +181,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace the selected cell's text with <br> element (even if the cell is column-spanning)");
|
"nsITableEditor.deleteTableCellContents() should replace the selected cell's text with <br> element (even if the cell is column-spanning)");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell element are selected (even if the cell is column-spanning)');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell element are selected (even if the cell is column-spanning)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -147,6 +192,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // Requires layout information.
|
editor.scrollTop; // Requires layout information.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().deleteTableCellContents();
|
getTableEditor().deleteTableCellContents();
|
||||||
@ -155,6 +201,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
"<tr><td>cell2-1</td><td>cell2-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.deleteTableCellContents() should replace a cell's text with <br> element when the cell contains selection range");
|
"nsITableEditor.deleteTableCellContents() should replace a cell's text with <br> element when the cell contains selection range");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when the cell contains selection range');
|
||||||
|
checkInputEvent(inputEvents[0], "when the cell contains selection range");
|
||||||
|
|
||||||
|
editor.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -19,17 +19,38 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
selection.collapse(editor.firstChild, 0);
|
selection.collapse(editor.firstChild, 0);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should do nothing if selection is not in <table>");
|
"nsITableEditor.deleteTableRow(1) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.deleteTableRow(1) does nothing');
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
ok(false, "getTableEditor().deleteTableRow(1) without selection ranges should throw exception");
|
ok(false, "getTableEditor().deleteTableRow(1) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().deleteTableRow(1) without selection ranges should throw exception");
|
ok(true, "getTableEditor().deleteTableRow(1) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.deleteTableRow(1) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a cell is selected and the argument is less than number of rows,
|
// If a cell is selected and the argument is less than number of rows,
|
||||||
@ -40,62 +61,86 @@ SimpleTest.waitForFocus(function() {
|
|||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
let range = document.createRange();
|
let range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete the first row when a cell in the first row is selected");
|
"nsITableEditor.deleteTableRow(1) should delete the first row when a cell in the first row is selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in the first row is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in the first row is selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete the second row when a cell in the second row is selected");
|
"nsITableEditor.deleteTableRow(1) should delete the second row when a cell in the second row is selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in the second row is selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in the second row is selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(2);
|
getTableEditor().deleteTableRow(2);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableRow(2) should delete the <table> since there is only 2 rows");
|
"nsITableEditor.deleteTableRow(2) should delete the <table> since there is only 2 rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in first row is selected and argument is same as number of rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in first row is selected and argument is same as number of rows");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(3);
|
getTableEditor().deleteTableRow(3);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableRow(3) should delete the <table> when argument is larger than actual number of rows");
|
"nsITableEditor.deleteTableRow(3) should delete the <table> when argument is larger than actual number of rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when argument is larger than actual number of rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when argument is larger than actual number of rows");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(2);
|
getTableEditor().deleteTableRow(2);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(2) should delete the second row containing selected cell and next row");
|
"nsITableEditor.deleteTableRow(2) should delete the second row containing selected cell and next row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in second row and argument is same as the remaining rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in second row and argument is same as the remaining rows");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(3);
|
getTableEditor().deleteTableRow(3);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(3) should delete the second row (containing selected cell) and the third row even though the argument is larger than the rows");
|
"nsITableEditor.deleteTableRow(3) should delete the second row (containing selected cell) and the third row even though the argument is larger than the rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in second row and argument is larger than the remaining rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in second row and argument is larger than the remaining rows");
|
||||||
|
|
||||||
// Similar to selected a cell, when selection is in a cell, the cell should
|
// Similar to selected a cell, when selection is in a cell, the cell should
|
||||||
// treated as selected.
|
// treated as selected.
|
||||||
@ -103,67 +148,91 @@ SimpleTest.waitForFocus(function() {
|
|||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select").firstChild);
|
range.selectNode(document.getElementById("select").firstChild);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete the first row when a cell in the first row contains selection range");
|
"nsITableEditor.deleteTableRow(1) should delete the first row when a cell in the first row contains selection range");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in the first row contains selection range');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in the first row contains selection range");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select").firstChild);
|
range.selectNode(document.getElementById("select").firstChild);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete the second row when a cell in the second row contains selection range");
|
"nsITableEditor.deleteTableRow(1) should delete the second row when a cell in the second row contains selection range");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in the second row contains selection range');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in the second row contains selection range");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select").firstChild);
|
range.selectNode(document.getElementById("select").firstChild);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(2);
|
getTableEditor().deleteTableRow(2);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableRow(2) should delete the <table> since there is only 2 rows");
|
"nsITableEditor.deleteTableRow(2) should delete the <table> since there is only 2 rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all text in a cell in first row is selected and argument includes next row');
|
||||||
|
checkInputEvent(inputEvents[0], "when all text in a cell in first row is selected and argument includes next row");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select").firstChild);
|
range.selectNode(document.getElementById("select").firstChild);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(3);
|
getTableEditor().deleteTableRow(3);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableRow(3) should delete the <table> when argument is larger than actual number of rows");
|
"nsITableEditor.deleteTableRow(3) should delete the <table> when argument is larger than actual number of rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all text in a cell in first row is selected and argument is same as number of all rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when all text in a cell in first row is selected and argument is same as number of all rows");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select").firstChild);
|
range.selectNode(document.getElementById("select").firstChild);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(2);
|
getTableEditor().deleteTableRow(2);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(2) should delete the second row containing a cell containing selection range and next row");
|
"nsITableEditor.deleteTableRow(2) should delete the second row containing a cell containing selection range and next row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all text in a cell is selected and argument is same than renaming number of rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when all text in a cell is selected and argument is same than renaming number of rows");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td>cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
editor.scrollTop; // Needs to flush pending reflow since we need layout information in this case.
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select").firstChild);
|
range.selectNode(document.getElementById("select").firstChild);
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(3);
|
getTableEditor().deleteTableRow(3);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell1-1</td><td>cell1-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(3) should delete the second row (containing selection range) and the third row even though the argument is larger than the rows");
|
"nsITableEditor.deleteTableRow(3) should delete the second row (containing selection range) and the third row even though the argument is larger than the rows");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when all text in a cell in the second row and argument is larger than renaming number of rows');
|
||||||
|
checkInputEvent(inputEvents[0], "when all text in a cell in the second row and argument is larger than renaming number of rows");
|
||||||
|
|
||||||
// The argument should be ignored when 2 or more cells are selected.
|
// The argument should be ignored when 2 or more cells are selected.
|
||||||
// XXX If the argument is less than number of rows and cells in all rows are
|
// XXX If the argument is less than number of rows and cells in all rows are
|
||||||
@ -172,6 +241,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -181,10 +251,14 @@ SimpleTest.waitForFocus(function() {
|
|||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody></tbody></table>",
|
is(editor.innerHTML, "<table><tbody></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete all rows if every row's cell is selected");
|
"nsITableEditor.deleteTableRow(1) should delete all rows if every row's cell is selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when cells in every row are selected #1');
|
||||||
|
checkInputEvent(inputEvents[0], "when cells in every row are selected #1");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -194,10 +268,14 @@ SimpleTest.waitForFocus(function() {
|
|||||||
getTableEditor().deleteTableRow(2);
|
getTableEditor().deleteTableRow(2);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableRow(2) should delete the <table> since 2 is number of rows of the <table>");
|
"nsITableEditor.deleteTableRow(2) should delete the <table> since 2 is number of rows of the <table>");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when cells in every row are selected #2');
|
||||||
|
checkInputEvent(inputEvents[0], "when cells in every row are selected #2");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
'<table><tr><td id="select1">cell1-1</td><td id="select2">cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -207,10 +285,14 @@ SimpleTest.waitForFocus(function() {
|
|||||||
getTableEditor().deleteTableRow(2);
|
getTableEditor().deleteTableRow(2);
|
||||||
is(editor.innerHTML, "",
|
is(editor.innerHTML, "",
|
||||||
"nsITableEditor.deleteTableRow(2) should delete the <table> since 2 is number of rows of the <table>");
|
"nsITableEditor.deleteTableRow(2) should delete the <table> since 2 is number of rows of the <table>");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when 2 cells in same row are selected');
|
||||||
|
checkInputEvent(inputEvents[0], "when 2 cells in same row are selected");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td id="select2">cell2-1</td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -220,10 +302,14 @@ SimpleTest.waitForFocus(function() {
|
|||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell3-1</td><td>cell3-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell3-1</td><td>cell3-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete first 2 rows because cells in the both rows are selected");
|
"nsITableEditor.deleteTableRow(1) should delete first 2 rows because cells in the both rows are selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when 2 cell elements in different rows are selected #1');
|
||||||
|
checkInputEvent(inputEvents[0], "when 2 cell elements in different rows are selected #1");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr><tr><td id="select2">cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td id="select1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-1</td><td>cell2-2</td></tr><tr><td id="select2">cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
@ -233,48 +319,69 @@ SimpleTest.waitForFocus(function() {
|
|||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, "<table><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>",
|
is(editor.innerHTML, "<table><tbody><tr><td>cell2-1</td><td>cell2-2</td></tr></tbody></table>",
|
||||||
"nsITableEditor.deleteTableRow(1) should delete the first and the last rows because cells in the both rows are selected");
|
"nsITableEditor.deleteTableRow(1) should delete the first and the last rows because cells in the both rows are selected");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when 2 cell elements in different rows are selected #2');
|
||||||
|
checkInputEvent(inputEvents[0], "when 2 cell elements in different rows are selected #2");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select" rowspan="2">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td id="select" rowspan="2">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, '<table><tbody><tr><td valign="top"><br></td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></tbody></table>',
|
is(editor.innerHTML, '<table><tbody><tr><td valign="top"><br></td><td>cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></tbody></table>',
|
||||||
"nsITableEditor.deleteTableRow(1) with a selected cell is rowspan=\"2\" should delete the first row and add empty cell to the second row");
|
"nsITableEditor.deleteTableRow(1) with a selected cell is rowspan=\"2\" should delete the first row and add empty cell to the second row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell is selected and its rowspan is 2');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell is selected and its rowspan is 2");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td id="select" rowspan="3">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-2</td></tr><tr><td>cell3-2</td></tr></table>';
|
'<table><tr><td id="select" rowspan="3">cell1-1</td><td>cell1-2</td></tr><tr><td>cell2-2</td></tr><tr><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, '<table><tbody><tr><td rowspan="2" valign="top"><br></td><td>cell2-2</td></tr><tr><td>cell3-2</td></tr></tbody></table>',
|
is(editor.innerHTML, '<table><tbody><tr><td rowspan="2" valign="top"><br></td><td>cell2-2</td></tr><tr><td>cell3-2</td></tr></tbody></table>',
|
||||||
"nsITableEditor.deleteTableRow(1) with a selected cell is rowspan=\"3\" should delete the first row and add empty cell whose rowspan is 2 to the second row");
|
"nsITableEditor.deleteTableRow(1) with a selected cell is rowspan=\"3\" should delete the first row and add empty cell whose rowspan is 2 to the second row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell is selected and its rowspan is 3');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell is selected and its rowspan is 3");
|
||||||
|
|
||||||
// XXX Must be buggy case. When removing a row which does not have a cell due
|
// XXX Must be buggy case. When removing a row which does not have a cell due
|
||||||
// to rowspan, the rowspan is not changed properly.
|
// to rowspan, the rowspan is not changed properly.
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td rowspan="3">cell1-1</td><td>cell1-2</td></tr><tr><td id="select1">cell2-2</td></tr><tr><td>cell3-2</td></tr></table>';
|
'<table><tr><td rowspan="3">cell1-1</td><td>cell1-2</td></tr><tr><td id="select1">cell2-2</td></tr><tr><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select1"));
|
range.selectNode(document.getElementById("select1"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, '<table><tbody><tr><td rowspan="1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell3-2</td></tr></tbody></table>',
|
is(editor.innerHTML, '<table><tbody><tr><td rowspan="1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell3-2</td></tr></tbody></table>',
|
||||||
"nsITableEditor.deleteTableRow(1) with selected cell in the second row should delete the second row and the row span should be adjusted");
|
"nsITableEditor.deleteTableRow(1) with selected cell in the second row should delete the second row and the row span should be adjusted");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in 2nd row which is only cell defined by the row #1');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in 2nd row which is only cell defined by the row #1");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML =
|
editor.innerHTML =
|
||||||
'<table><tr><td rowspan="2">cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
'<table><tr><td rowspan="2">cell1-1</td><td>cell1-2</td></tr><tr><td id="select">cell2-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></table>';
|
||||||
|
inputEvents = [];
|
||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.selectNode(document.getElementById("select"));
|
range.selectNode(document.getElementById("select"));
|
||||||
selection.addRange(range);
|
selection.addRange(range);
|
||||||
getTableEditor().deleteTableRow(1);
|
getTableEditor().deleteTableRow(1);
|
||||||
is(editor.innerHTML, '<table><tbody><tr><td rowspan="1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></tbody></table>',
|
is(editor.innerHTML, '<table><tbody><tr><td rowspan="1">cell1-1</td><td>cell1-2</td></tr><tr><td>cell3-1</td><td>cell3-2</td></tr></tbody></table>',
|
||||||
"nsITableEditor.deleteTableRow(1) with selected cell in the second row should delete the second row and the row span should be adjusted");
|
"nsITableEditor.deleteTableRow(1) with selected cell in the second row should delete the second row and the row span should be adjusted");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when a cell in 2nd row which is only cell defined by the row #2');
|
||||||
|
checkInputEvent(inputEvents[0], "when a cell in 2nd row which is only cell defined by the row #2");
|
||||||
|
|
||||||
|
editor.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -19,26 +19,54 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
selection.collapse(editor.firstChild, 0);
|
selection.collapse(editor.firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, false);
|
getTableEditor().insertTableCell(1, false);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, false) should do nothing if selection is not in <table>");
|
"nsITableEditor.insertTableCell(1, false) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.insertTableCell(1, false) does nothing');
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableCell(1, true);
|
getTableEditor().insertTableCell(1, true);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, true) should do nothing if selection is not in <table>");
|
"nsITableEditor.insertTableCell(1, true) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.insertTableCell(1, true) does nothing');
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableCell(1, false);
|
getTableEditor().insertTableCell(1, false);
|
||||||
ok(false, "getTableEditor().insertTableCell(1, false) without selection ranges should throw exception");
|
ok(false, "getTableEditor().insertTableCell(1, false) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().insertTableCell(1, false) without selection ranges should throw exception");
|
ok(true, "getTableEditor().insertTableCell(1, false) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.insertTableCell(1, false) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableCell(1, true);
|
getTableEditor().insertTableCell(1, true);
|
||||||
ok(false, "getTableEditor().insertTableCell(1, true) without selection ranges should throw exception");
|
ok(false, "getTableEditor().insertTableCell(1, true) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().insertTableCell(1, true) without selection ranges should throw exception");
|
ok(true, "getTableEditor().insertTableCell(1, true) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.insertTableCell(1, true) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -49,6 +77,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, false);
|
getTableEditor().insertTableCell(1, false);
|
||||||
@ -58,6 +87,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, false) should insert a cell before the cell containing selection");
|
"nsITableEditor.insertTableCell(1, false) should insert a cell before the cell containing selection");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell in middle row (before)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell in middle row (before)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -67,6 +101,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, true);
|
getTableEditor().insertTableCell(1, true);
|
||||||
@ -76,6 +111,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, true) should insert a cell after the cell containing selection");
|
"nsITableEditor.insertTableCell(1, true) should insert a cell after the cell containing selection");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell in middle row (after)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell in middle row (after)");
|
||||||
|
}
|
||||||
|
|
||||||
// with rowspan.
|
// with rowspan.
|
||||||
|
|
||||||
@ -88,6 +128,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, false);
|
getTableEditor().insertTableCell(1, false);
|
||||||
@ -97,6 +138,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, false) should insert a cell before the cell containing selection and moves the cell to right of the row-spanning cell element");
|
"nsITableEditor.insertTableCell(1, false) should insert a cell before the cell containing selection and moves the cell to right of the row-spanning cell element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell in middle row and it has row-spanned cell (before)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell in middle row and it has row-spanned cell (before)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -106,6 +152,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, true);
|
getTableEditor().insertTableCell(1, true);
|
||||||
@ -115,6 +162,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td></tr>" +
|
"<tr><td>cell3-1</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, true) should insert a cell after the cell containing selection and moves the cell to right of the row-spanning cell element");
|
"nsITableEditor.insertTableCell(1, true) should insert a cell after the cell containing selection and moves the cell to right of the row-spanning cell element");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell in middle row and it has row-spanned cell (after)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell in middle row and it has row-spanned cell (after)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -124,6 +176,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableCell(2, false);
|
getTableEditor().insertTableCell(2, false);
|
||||||
@ -133,6 +186,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(2, false) should insert 2 cells before the row-spanning cell containing selection");
|
"nsITableEditor.insertTableCell(2, false) should insert 2 cells before the row-spanning cell containing selection");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell in row-spanning (before)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell in row-spanning (before)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -142,6 +200,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableCell(2, true);
|
getTableEditor().insertTableCell(2, true);
|
||||||
@ -151,6 +210,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(2, false) should insert 2 cells after the row-spanning cell containing selection");
|
"nsITableEditor.insertTableCell(2, false) should insert 2 cells after the row-spanning cell containing selection");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell in row-spanning (after)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell in row-spanning (after)");
|
||||||
|
}
|
||||||
|
|
||||||
// with colspan
|
// with colspan
|
||||||
|
|
||||||
@ -161,6 +225,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, false);
|
getTableEditor().insertTableCell(1, false);
|
||||||
@ -169,6 +234,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td colspan="2">cell2-1</td><td>cell2-3</td></tr>' +
|
'<tr><td colspan="2">cell2-1</td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, false) should insert a cell before the cell containing selection but do not modify col-spanning cell");
|
"nsITableEditor.insertTableCell(1, false) should insert a cell before the cell containing selection but do not modify col-spanning cell");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell whose next row cell is col-spanned (before)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell whose next row cell is col-spanned (before)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -177,6 +247,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableCell(1, true);
|
getTableEditor().insertTableCell(1, true);
|
||||||
@ -185,6 +256,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td colspan="3">cell2-1</td></tr>' +
|
'<tr><td colspan="3">cell2-1</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(1, true) should insert a cell after the cell containing selection but do not modify col-spanning cell");
|
"nsITableEditor.insertTableCell(1, true) should insert a cell after the cell containing selection but do not modify col-spanning cell");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell whose next row cell is col-spanned (after)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell whose next row cell is col-spanned (after)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -193,6 +269,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableCell(2, false);
|
getTableEditor().insertTableCell(2, false);
|
||||||
@ -201,6 +278,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td valign="top"><br></td><td valign="top"><br></td><td id="select" colspan="2">cell2-1</td><td>cell2-3</td></tr>' +
|
'<tr><td valign="top"><br></td><td valign="top"><br></td><td id="select" colspan="2">cell2-1</td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(2, false) should insert 2 cells before the col-spanning cell containing selection");
|
"nsITableEditor.insertTableCell(2, false) should insert 2 cells before the col-spanning cell containing selection");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell which is col-spanning (before)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell which is col-spanning (before)");
|
||||||
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -209,6 +291,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableCell(2, true);
|
getTableEditor().insertTableCell(2, true);
|
||||||
@ -217,6 +300,13 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select" colspan="2">cell2-1</td><td valign="top"><br></td><td valign="top"><br></td><td>cell2-3</td></tr>' +
|
'<tr><td id="select" colspan="2">cell2-1</td><td valign="top"><br></td><td valign="top"><br></td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableCell(2, false) should insert 2 cells after the col-spanning cell containing selection");
|
"nsITableEditor.insertTableCell(2, false) should insert 2 cells after the col-spanning cell containing selection");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection collapsed in a cell which is col-spanning (after)');
|
||||||
|
if (inputEvents.length > 0) {
|
||||||
|
checkInputEvent(inputEvents[0], "when selection collapsed in a cell which is col-spanning (after)");
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -19,26 +19,54 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
selection.collapse(editor.firstChild, 0);
|
selection.collapse(editor.firstChild, 0);
|
||||||
getTableEditor().insertTableColumn(1, false);
|
getTableEditor().insertTableColumn(1, false);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(1, false) should do nothing if selection is not in <table>");
|
"nsITableEditor.insertTableColumn(1, false) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.insertTableColumn(1, false) does nothing');
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableColumn(1, true);
|
getTableEditor().insertTableColumn(1, true);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(1, true) should do nothing if selection is not in <table>");
|
"nsITableEditor.insertTableColumn(1, true) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.insertTableColumn(1, true) does nothing');
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableColumn(1, false);
|
getTableEditor().insertTableColumn(1, false);
|
||||||
ok(false, "getTableEditor().insertTableColumn(1, false) without selection ranges should throw exception");
|
ok(false, "getTableEditor().insertTableColumn(1, false) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().insertTableColumn(1, false) without selection ranges should throw exception");
|
ok(true, "getTableEditor().insertTableColumn(1, false) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.insertTableColumn(1, false) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableColumn(1, true);
|
getTableEditor().insertTableColumn(1, true);
|
||||||
ok(false, "getTableEditor().insertTableColumn(1, true) without selection ranges should throw exception");
|
ok(false, "getTableEditor().insertTableColumn(1, true) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().insertTableColumn(1, true) without selection ranges should throw exception");
|
ok(true, "getTableEditor().insertTableColumn(1, true) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.insertTableColumn(1, true) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -48,6 +76,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableColumn(1, false);
|
getTableEditor().insertTableColumn(1, false);
|
||||||
@ -56,6 +85,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td>cell2-1</td><td valign="top"><br></td><td>cell2-2</td><td>cell2-3</td></tr>' +
|
'<tr><td>cell2-1</td><td valign="top"><br></td><td>cell2-2</td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(1, false) should insert a column to left of the second column");
|
"nsITableEditor.insertTableColumn(1, false) should insert a column to left of the second column");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second column (before)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second column (before)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -64,6 +96,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableColumn(1, true);
|
getTableEditor().insertTableColumn(1, true);
|
||||||
@ -72,6 +105,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td>cell2-1</td><td>cell2-2</td><td valign="top"><br></td><td>cell2-3</td></tr>' +
|
'<tr><td>cell2-1</td><td>cell2-2</td><td valign="top"><br></td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(1, false) should insert a column to right of the second column");
|
"nsITableEditor.insertTableColumn(1, false) should insert a column to right of the second column");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second column (after)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second column (after)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -80,6 +116,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableColumn(1, false);
|
getTableEditor().insertTableColumn(1, false);
|
||||||
@ -88,6 +125,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td colspan="3">cell2-1</td><td>cell2-3</td></tr>' +
|
'<tr><td colspan="3">cell2-1</td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(1, false) should insert a column to left of the second column and colspan in the first column should be increased");
|
"nsITableEditor.insertTableColumn(1, false) should insert a column to left of the second column and colspan in the first column should be increased");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second column whose next row cell is col-spanned (before)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second column whose next row cell is col-spanned (before)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -96,6 +136,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableColumn(1, true);
|
getTableEditor().insertTableColumn(1, true);
|
||||||
@ -104,6 +145,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td colspan="4">cell2-1</td></tr>' +
|
'<tr><td colspan="4">cell2-1</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(1, true) should insert a column to right of the second column and colspan in the first column should be increased");
|
"nsITableEditor.insertTableColumn(1, true) should insert a column to right of the second column and colspan in the first column should be increased");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second column whose next row cell is col-spanned (after)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second column whose next row cell is col-spanned (after)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -112,6 +156,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableColumn(2, false);
|
getTableEditor().insertTableColumn(2, false);
|
||||||
@ -120,6 +165,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td valign="top"><br></td><td valign="top"><br></td><td id="select" colspan="2">cell2-1</td><td>cell2-3</td></tr>' +
|
'<tr><td valign="top"><br></td><td valign="top"><br></td><td id="select" colspan="2">cell2-1</td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(2, false) should insert 2 columns to left of the first column");
|
"nsITableEditor.insertTableColumn(2, false) should insert 2 columns to left of the first column");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell which is col-spanning (before)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell which is col-spanning (before)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -128,6 +176,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableColumn(2, true);
|
getTableEditor().insertTableColumn(2, true);
|
||||||
@ -136,6 +185,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
'<tr><td id="select" colspan="2">cell2-1</td><td valign="top"><br></td><td valign="top"><br></td><td>cell2-3</td></tr>' +
|
'<tr><td id="select" colspan="2">cell2-1</td><td valign="top"><br></td><td valign="top"><br></td><td>cell2-3</td></tr>' +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableColumn(2, false) should insert 2 columns to right of the second column (i.e., right of the right-most column of the column-spanning cell");
|
"nsITableEditor.insertTableColumn(2, false) should insert 2 columns to right of the second column (i.e., right of the right-most column of the column-spanning cell");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell which is col-spanning (after)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell which is col-spanning (after)");
|
||||||
|
|
||||||
|
editor.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -19,26 +19,54 @@ SimpleTest.waitForFocus(function() {
|
|||||||
let editor = document.getElementById("content");
|
let editor = document.getElementById("content");
|
||||||
let selection = document.getSelection();
|
let selection = document.getSelection();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editor.addEventListener("input", onInput);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
selection.collapse(editor.firstChild, 0);
|
selection.collapse(editor.firstChild, 0);
|
||||||
getTableEditor().insertTableRow(1, false);
|
getTableEditor().insertTableRow(1, false);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(1, false) should do nothing if selection is not in <table>");
|
"nsITableEditor.insertTableRow(1, false) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.insertTableRow(1, false) does nothing');
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableRow(1, true);
|
getTableEditor().insertTableRow(1, true);
|
||||||
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
is(editor.innerHTML, "out of table<table><tbody><tr><td>default content</td></tr></tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(1, true) should do nothing if selection is not in <table>");
|
"nsITableEditor.insertTableRow(1, true) should do nothing if selection is not in <table>");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when a call of nsITableEditor.insertTableRow(1, true) does nothing');
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableRow(1, false);
|
getTableEditor().insertTableRow(1, false);
|
||||||
ok(false, "getTableEditor().insertTableRow(1, false) without selection ranges should throw exception");
|
ok(false, "getTableEditor().insertTableRow(1, false) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().insertTableRow(1, false) without selection ranges should throw exception");
|
ok(true, "getTableEditor().insertTableRow(1, false) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.insertTableRow(1, false) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
inputEvents = [];
|
||||||
getTableEditor().insertTableRow(1, true);
|
getTableEditor().insertTableRow(1, true);
|
||||||
ok(false, "getTableEditor().insertTableRow(1, true) without selection ranges should throw exception");
|
ok(false, "getTableEditor().insertTableRow(1, true) without selection ranges should throw exception");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ok(true, "getTableEditor().insertTableRow(1, true) without selection ranges should throw exception");
|
ok(true, "getTableEditor().insertTableRow(1, true) without selection ranges should throw exception");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
'No "input" event should be fired when nsITableEditor.insertTableRow(1, true) causes exception due to no selection range');
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
@ -49,6 +77,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableRow(1, false);
|
getTableEditor().insertTableRow(1, false);
|
||||||
@ -59,6 +88,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(1, false) should insert a row above the second row");
|
"nsITableEditor.insertTableRow(1, false) should insert a row above the second row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second row (before)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second row (before)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -68,6 +100,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableRow(1, true);
|
getTableEditor().insertTableRow(1, true);
|
||||||
@ -78,6 +111,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(1, true) should insert a row below the second row");
|
"nsITableEditor.insertTableRow(1, true) should insert a row below the second row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second row (after)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second row (after)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -87,6 +123,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableRow(1, false);
|
getTableEditor().insertTableRow(1, false);
|
||||||
@ -97,6 +134,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(1, false) should insert a row above the second row and rowspan in the first row should be increased");
|
"nsITableEditor.insertTableRow(1, false) should insert a row above the second row and rowspan in the first row should be increased");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second row which has row-spanned cell (before)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second row which has row-spanned cell (before)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -106,6 +146,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 0);
|
document.getElementById("select").firstChild, 0);
|
||||||
getTableEditor().insertTableRow(1, true);
|
getTableEditor().insertTableRow(1, true);
|
||||||
@ -116,6 +157,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td></tr>" +
|
"<tr><td>cell3-1</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(1, true) should insert a row below the second row and rowspan in the first row should be increased");
|
"nsITableEditor.insertTableRow(1, true) should insert a row below the second row and rowspan in the first row should be increased");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell in second row which has row-spanned cell (after)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell in second row which has row-spanned cell (after)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -125,6 +169,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableRow(2, false);
|
getTableEditor().insertTableRow(2, false);
|
||||||
@ -136,6 +181,9 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(2, false) should insert 2 rows above the first row");
|
"nsITableEditor.insertTableRow(2, false) should insert 2 rows above the first row");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell which is row-spanning (before)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell which is row-spanning (before)");
|
||||||
|
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
editor.innerHTML = "<table>" +
|
editor.innerHTML = "<table>" +
|
||||||
@ -145,6 +193,7 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"</table>";
|
"</table>";
|
||||||
editor.focus();
|
editor.focus();
|
||||||
editor.scrollTop; // layout information required.
|
editor.scrollTop; // layout information required.
|
||||||
|
inputEvents = [];
|
||||||
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
selection.setBaseAndExtent(document.getElementById("select").firstChild, 0,
|
||||||
document.getElementById("select").firstChild, 1);
|
document.getElementById("select").firstChild, 1);
|
||||||
getTableEditor().insertTableRow(2, true);
|
getTableEditor().insertTableRow(2, true);
|
||||||
@ -156,6 +205,11 @@ SimpleTest.waitForFocus(function() {
|
|||||||
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
"<tr><td>cell3-1</td><td>cell3-2</td></tr>" +
|
||||||
"</tbody></table>",
|
"</tbody></table>",
|
||||||
"nsITableEditor.insertTableRow(2, false) should insert 2 rows below the second row (i.e., below the bottom row of the row-spanning cell");
|
"nsITableEditor.insertTableRow(2, false) should insert 2 rows below the second row (i.e., below the bottom row of the row-spanning cell");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when selection is collapsed in a cell which is row-spanning (after)');
|
||||||
|
checkInputEvent(inputEvents[0], "when selection is collapsed in a cell which is row-spanning (after)");
|
||||||
|
|
||||||
|
editor.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -85,12 +85,31 @@ SimpleTest.waitForFocus(async function() {
|
|||||||
let basePosX = rect.width * baseX;
|
let basePosX = rect.width * baseX;
|
||||||
let basePosY = rect.height * baseY;
|
let basePosY = rect.height * baseY;
|
||||||
|
|
||||||
|
let inputEventExpected = true;
|
||||||
|
function onInput(aEvent) {
|
||||||
|
if (!inputEventExpected) {
|
||||||
|
ok(false, "\"input\" event shouldn't be fired after stopping resizing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
'"input" event should be dispatched with InputEvent interface');
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
'"input" event should be never cancelable');
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
'"input" event should always bubble');
|
||||||
|
}
|
||||||
|
|
||||||
|
content.addEventListener("input", onInput);
|
||||||
|
|
||||||
// Click on the correct resizer
|
// Click on the correct resizer
|
||||||
synthesizeMouse(target, basePosX, basePosY, {type: "mousedown"});
|
synthesizeMouse(target, basePosX, basePosY, {type: "mousedown"});
|
||||||
// Drag it delta pixels to the right and bottom (or maybe left and top!)
|
// Drag it delta pixels to the right and bottom (or maybe left and top!)
|
||||||
synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mousemove"});
|
synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mousemove"});
|
||||||
// Release the mouse button
|
// Release the mouse button
|
||||||
synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mouseup"});
|
synthesizeMouse(target, basePosX + deltaX, basePosY + deltaY, {type: "mouseup"});
|
||||||
|
|
||||||
|
inputEventExpected = false;
|
||||||
|
|
||||||
// Move the mouse delta more pixels to the same direction to make sure that the
|
// Move the mouse delta more pixels to the same direction to make sure that the
|
||||||
// resize operation has stopped.
|
// resize operation has stopped.
|
||||||
synthesizeMouse(target, basePosX + deltaX * 2, basePosY + deltaY * 2, {type: "mousemove"});
|
synthesizeMouse(target, basePosX + deltaX * 2, basePosY + deltaY * 2, {type: "mousemove"});
|
||||||
@ -104,6 +123,8 @@ SimpleTest.waitForFocus(async function() {
|
|||||||
let newRect = target.getBoundingClientRect();
|
let newRect = target.getBoundingClientRect();
|
||||||
isfuzzy(newRect.width, rect.width + expectedDeltaX, 2, description + "The width should be increased by " + expectedDeltaX + " pixels");
|
isfuzzy(newRect.width, rect.width + expectedDeltaX, 2, description + "The width should be increased by " + expectedDeltaX + " pixels");
|
||||||
isfuzzy(newRect.height, rect.height + expectedDeltaY, 2, description + "The height should be increased by " + expectedDeltaY + "pixels");
|
isfuzzy(newRect.height, rect.height + expectedDeltaY, 2, description + "The height should be increased by " + expectedDeltaY + "pixels");
|
||||||
|
|
||||||
|
content.removeEventListener("input", onInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account for changes in the resizing behavior when we're trying to preserve
|
// Account for changes in the resizing behavior when we're trying to preserve
|
||||||
|
@ -21,24 +21,56 @@ SimpleTest.waitForFocus(() => {
|
|||||||
|
|
||||||
textarea.focus();
|
textarea.focus();
|
||||||
|
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
|
||||||
SpecialPowers.Cu.import(
|
SpecialPowers.Cu.import(
|
||||||
"resource://testing-common/AsyncSpellCheckTestHelper.jsm")
|
"resource://testing-common/AsyncSpellCheckTestHelper.jsm")
|
||||||
.onSpellCheck(textarea, () => {
|
.onSpellCheck(textarea, () => {
|
||||||
SimpleTest.executeSoon(() => {
|
SimpleTest.executeSoon(() => {
|
||||||
|
textarea.addEventListener("input", onInput);
|
||||||
|
|
||||||
let misspelledWord = inlineSpellChecker.getMisspelledWord(editor.rootElement.firstChild, 5);
|
let misspelledWord = inlineSpellChecker.getMisspelledWord(editor.rootElement.firstChild, 5);
|
||||||
is(misspelledWord.startOffset, 4,
|
is(misspelledWord.startOffset, 4,
|
||||||
"Misspelled word should start from 4");
|
"Misspelled word should start from 4");
|
||||||
is(misspelledWord.endOffset, 7,
|
is(misspelledWord.endOffset, 7,
|
||||||
"Misspelled word should end at 7");
|
"Misspelled word should end at 7");
|
||||||
|
inputEvents = [];
|
||||||
inlineSpellChecker.replaceWord(editor.rootElement.firstChild, 5, "aux");
|
inlineSpellChecker.replaceWord(editor.rootElement.firstChild, 5, "aux");
|
||||||
is(textarea.value, "abc aux abc",
|
is(textarea.value, "abc aux abc",
|
||||||
"'abx' should be replaced with 'aux'");
|
"'abx' should be replaced with 'aux'");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when replacing a word with spellchecker');
|
||||||
|
checkInputEvent(inputEvents[0], "when replacing a word with spellchecker");
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("z", { accelKey: true });
|
synthesizeKey("z", { accelKey: true });
|
||||||
is(textarea.value, "abc abx abc",
|
is(textarea.value, "abc abx abc",
|
||||||
"'abx' should be restored by undo");
|
"'abx' should be restored by undo");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when undoing the replacing word');
|
||||||
|
checkInputEvent(inputEvents[0], "when undoing the replacing word");
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("z", { accelKey: true, shiftKey: true });
|
synthesizeKey("z", { accelKey: true, shiftKey: true });
|
||||||
is(textarea.value, "abc aux abc",
|
is(textarea.value, "abc aux abc",
|
||||||
"'aux' should be restored by redo");
|
"'aux' should be restored by redo");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
'Only one "input" event should be fired when redoing the replacing word');
|
||||||
|
checkInputEvent(inputEvents[0], "when redoing the replacing word");
|
||||||
|
|
||||||
|
textarea.removeEventListener("input", onInput);
|
||||||
|
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -29,11 +29,46 @@ SimpleTest.waitForFocus(function() {
|
|||||||
document.getElementById("textarea"),
|
document.getElementById("textarea"),
|
||||||
];
|
];
|
||||||
for (let editableElement of editableElements) {
|
for (let editableElement of editableElements) {
|
||||||
|
function checkInputEvent(aEvent, aDescription) {
|
||||||
|
ok(aEvent instanceof InputEvent,
|
||||||
|
`"input" event should be dispatched with InputEvent interface ${aDescription}`);
|
||||||
|
is(aEvent.cancelable, false,
|
||||||
|
`"input" event should be never cancelable ${aDescription}`);
|
||||||
|
is(aEvent.bubbles, true,
|
||||||
|
`"input" event should always bubble ${aDescription}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let inputEvents = [];
|
||||||
|
function onInput(aEvent) {
|
||||||
|
inputEvents.push(aEvent);
|
||||||
|
}
|
||||||
|
editableElement.addEventListener("input", onInput);
|
||||||
|
|
||||||
editableElement.focus();
|
editableElement.focus();
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("a");
|
synthesizeKey("a");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be fired when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
checkInputEvent(inputEvents[0], `when inserting "a" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("c");
|
synthesizeKey("c");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be fired when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
checkInputEvent(inputEvents[0], `when inserting "c" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("KEY_ArrowLeft");
|
synthesizeKey("KEY_ArrowLeft");
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be fired when pressing "ArrowLeft" key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("b");
|
synthesizeKey("b");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be fired when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
checkInputEvent(inputEvents[0], `when inserting "b" with key on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
let editor = SpecialPowers.wrap(editableElement).editor;
|
let editor = SpecialPowers.wrap(editableElement).editor;
|
||||||
let transactionManager = editor.transactionManager;
|
let transactionManager = editor.transactionManager;
|
||||||
is(transactionManager.numberOfUndoItems, 2,
|
is(transactionManager.numberOfUndoItems, 2,
|
||||||
@ -41,15 +76,30 @@ SimpleTest.waitForFocus(function() {
|
|||||||
// Defined as nsITextControlElement::DEFAULT_UNDO_CAP
|
// Defined as nsITextControlElement::DEFAULT_UNDO_CAP
|
||||||
is(transactionManager.maxTransactionCount, 1000,
|
is(transactionManager.maxTransactionCount, 1000,
|
||||||
editableElement.tagName + ": Initially, transaction manager should be able to have 1,000 undo items");
|
editableElement.tagName + ": Initially, transaction manager should be able to have 1,000 undo items");
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
editableElement.value = "def";
|
editableElement.value = "def";
|
||||||
|
is(inputEvents.length, 0,
|
||||||
|
`No "input" event should be fired when setting value of <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
is(transactionManager.numberOfUndoItems, 0,
|
is(transactionManager.numberOfUndoItems, 0,
|
||||||
editableElement.tagName + ": After setting value, all undo items must be deleted");
|
editableElement.tagName + ": After setting value, all undo items must be deleted");
|
||||||
is(transactionManager.maxTransactionCount, 1000,
|
is(transactionManager.maxTransactionCount, 1000,
|
||||||
editableElement.tagName + ": After setting value, maximum transaction count should be restored to the previous value");
|
editableElement.tagName + ": After setting value, maximum transaction count should be restored to the previous value");
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("a");
|
synthesizeKey("a");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be fired when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
checkInputEvent(inputEvents[0], `when inserting "a" with key again on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
|
inputEvents = [];
|
||||||
synthesizeKey("z", { accelKey: true });
|
synthesizeKey("z", { accelKey: true });
|
||||||
is(editableElement.value, "def",
|
is(editableElement.value, "def",
|
||||||
editableElement.tagName + ": undo should work after setting value");
|
editableElement.tagName + ": undo should work after setting value");
|
||||||
|
is(inputEvents.length, 1,
|
||||||
|
`Only one "input" event should be fired when undoing on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
checkInputEvent(inputEvents[0], `when undoing on <${editableElement.tagName.toLowerCase()}> element`);
|
||||||
|
|
||||||
// Disable undo/redo.
|
// Disable undo/redo.
|
||||||
editor.enableUndo(0);
|
editor.enableUndo(0);
|
||||||
@ -58,6 +108,8 @@ SimpleTest.waitForFocus(function() {
|
|||||||
editableElement.value = "hij";
|
editableElement.value = "hij";
|
||||||
is(transactionManager.maxTransactionCount, 0,
|
is(transactionManager.maxTransactionCount, 0,
|
||||||
editableElement.tagName + ": Transaction manager should not be able to have undo items after setting value");
|
editableElement.tagName + ": Transaction manager should not be able to have undo items after setting value");
|
||||||
|
|
||||||
|
editableElement.removeEventListener("input", onInput);
|
||||||
}
|
}
|
||||||
SimpleTest.finish();
|
SimpleTest.finish();
|
||||||
});
|
});
|
||||||
|
@ -191,8 +191,12 @@ impl SpatialNode {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_uninvertible(&mut self) {
|
pub fn mark_uninvertible(
|
||||||
|
&mut self,
|
||||||
|
state: &TransformUpdateState,
|
||||||
|
) {
|
||||||
self.invertible = false;
|
self.invertible = false;
|
||||||
|
self.coordinate_system_id = state.current_coordinate_system_id;
|
||||||
self.world_content_transform = LayoutToWorldFastTransform::identity();
|
self.world_content_transform = LayoutToWorldFastTransform::identity();
|
||||||
self.world_viewport_transform = LayoutToWorldFastTransform::identity();
|
self.world_viewport_transform = LayoutToWorldFastTransform::identity();
|
||||||
}
|
}
|
||||||
@ -221,7 +225,7 @@ impl SpatialNode {
|
|||||||
// If any of our parents was not rendered, we are not rendered either and can just
|
// If any of our parents was not rendered, we are not rendered either and can just
|
||||||
// quit here.
|
// quit here.
|
||||||
if !state.invertible {
|
if !state.invertible {
|
||||||
self.mark_uninvertible();
|
self.mark_uninvertible(state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +237,7 @@ impl SpatialNode {
|
|||||||
// translations which should be invertible.
|
// translations which should be invertible.
|
||||||
match self.node_type {
|
match self.node_type {
|
||||||
SpatialNodeType::ReferenceFrame(info) if !info.invertible => {
|
SpatialNodeType::ReferenceFrame(info) if !info.invertible => {
|
||||||
self.mark_uninvertible();
|
self.mark_uninvertible(state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ => self.invertible = true,
|
_ => self.invertible = true,
|
||||||
|
@ -1 +1 @@
|
|||||||
6ffc7cfe02f2a914a7d4338510277988643cc441
|
229436b578701fc74a009d6cedc6b2a3ae313f77
|
||||||
|
@ -128,6 +128,7 @@ public:
|
|||||||
* @param aRepaint if true then force repaint (NOTE: we always force repaint currently)
|
* @param aRepaint if true then force repaint (NOTE: we always force repaint currently)
|
||||||
* @note This method might destroy |this|.
|
* @note This method might destroy |this|.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
virtual void SetFocus(bool aOn, bool aRepaint) override;
|
virtual void SetFocus(bool aOn, bool aRepaint) override;
|
||||||
|
|
||||||
bool IsDroppedDown() { return mDroppedDown; }
|
bool IsDroppedDown() { return mDroppedDown; }
|
||||||
|
@ -459,11 +459,11 @@ nsFileControlFrame::DnDListener::HandleEvent(Event* aEvent)
|
|||||||
inputElement->SetFiles(fileList, true);
|
inputElement->SetFiles(fileList, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
|
RefPtr<TextEditor> textEditor;
|
||||||
static_cast<nsINode*>(inputElement),
|
DebugOnly<nsresult> rvIgnored =
|
||||||
NS_LITERAL_STRING("input"),
|
nsContentUtils::DispatchInputEvent(inputElement);
|
||||||
CanBubble::eYes,
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
Cancelable::eNo);
|
"Failed to dispatch input event");
|
||||||
nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
|
nsContentUtils::DispatchTrustedEvent(inputElement->OwnerDoc(),
|
||||||
static_cast<nsINode*>(inputElement),
|
static_cast<nsINode*>(inputElement),
|
||||||
NS_LITERAL_STRING("change"),
|
NS_LITERAL_STRING("change"),
|
||||||
|
@ -123,7 +123,9 @@ protected:
|
|||||||
: MouseListener(aFrame)
|
: MouseListener(aFrame)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
NS_DECL_NSIDOMEVENTLISTENER
|
// nsIDOMEventListener
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
|
NS_IMETHOD HandleEvent(mozilla::dom::Event* aEvent) override;
|
||||||
|
|
||||||
nsresult GetBlobImplForWebkitDirectory(mozilla::dom::FileList* aFileList,
|
nsresult GetBlobImplForWebkitDirectory(mozilla::dom::FileList* aFileList,
|
||||||
mozilla::dom::BlobImpl** aBlobImpl);
|
mozilla::dom::BlobImpl** aBlobImpl);
|
||||||
|
@ -70,7 +70,10 @@ public:
|
|||||||
void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
|
void SetFrame(nsListControlFrame *aFrame) { mFrame = aFrame; }
|
||||||
|
|
||||||
NS_DECL_ISUPPORTS
|
NS_DECL_ISUPPORTS
|
||||||
NS_DECL_NSIDOMEVENTLISTENER
|
|
||||||
|
// nsIDOMEventListener
|
||||||
|
MOZ_CAN_RUN_SCRIPT_BOUNDARY
|
||||||
|
NS_IMETHOD HandleEvent(Event* aEvent) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~nsListEventListener() {}
|
~nsListEventListener() {}
|
||||||
@ -1390,14 +1393,17 @@ nsListControlFrame::FireOnInputAndOnChange()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsIContent> content = mContent;
|
RefPtr<Element> element = Element::FromNodeOrNull(mContent);
|
||||||
|
if (NS_WARN_IF(!element)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Dispatch the input event.
|
// Dispatch the input event.
|
||||||
nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
|
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(element);
|
||||||
NS_LITERAL_STRING("input"),
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||||
CanBubble::eYes,
|
"Failed to dispatch input event");
|
||||||
Cancelable::eNo);
|
|
||||||
// Dispatch the change event.
|
// Dispatch the change event.
|
||||||
nsContentUtils::DispatchTrustedEvent(content->OwnerDoc(), content,
|
nsContentUtils::DispatchTrustedEvent(element->OwnerDoc(), element,
|
||||||
NS_LITERAL_STRING("change"),
|
NS_LITERAL_STRING("change"),
|
||||||
CanBubble::eYes,
|
CanBubble::eYes,
|
||||||
Cancelable::eNo);
|
Cancelable::eNo);
|
||||||
|
@ -134,6 +134,7 @@ public:
|
|||||||
* Dispatch a DOM oninput and onchange event synchroniously.
|
* Dispatch a DOM oninput and onchange event synchroniously.
|
||||||
* @note This method might destroy the frame, pres shell and other objects.
|
* @note This method might destroy the frame, pres shell and other objects.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void FireOnInputAndOnChange();
|
void FireOnInputAndOnChange();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,10 +161,13 @@ public:
|
|||||||
* @note These methods might destroy the frame, pres shell and other objects.
|
* @note These methods might destroy the frame, pres shell and other objects.
|
||||||
*/
|
*/
|
||||||
nsresult MouseDown(mozilla::dom::Event* aMouseEvent);
|
nsresult MouseDown(mozilla::dom::Event* aMouseEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult MouseUp(mozilla::dom::Event* aMouseEvent);
|
nsresult MouseUp(mozilla::dom::Event* aMouseEvent);
|
||||||
nsresult MouseMove(mozilla::dom::Event* aMouseEvent);
|
nsresult MouseMove(mozilla::dom::Event* aMouseEvent);
|
||||||
nsresult DragMove(mozilla::dom::Event* aMouseEvent);
|
nsresult DragMove(mozilla::dom::Event* aMouseEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult KeyDown(mozilla::dom::Event* aKeyEvent);
|
nsresult KeyDown(mozilla::dom::Event* aKeyEvent);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
nsresult KeyPress(mozilla::dom::Event* aKeyEvent);
|
nsresult KeyPress(mozilla::dom::Event* aKeyEvent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -257,6 +261,7 @@ protected:
|
|||||||
* @note This method might destroy the frame, pres shell and other objects.
|
* @note This method might destroy the frame, pres shell and other objects.
|
||||||
* Returns false if calling it destroyed |this|.
|
* Returns false if calling it destroyed |this|.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
bool UpdateSelection();
|
bool UpdateSelection();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -271,6 +276,7 @@ protected:
|
|||||||
* Toggles (show/hide) the combobox dropdown menu.
|
* Toggles (show/hide) the combobox dropdown menu.
|
||||||
* @note This method might destroy the frame, pres shell and other objects.
|
* @note This method might destroy the frame, pres shell and other objects.
|
||||||
*/
|
*/
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void DropDownToggleKey(mozilla::dom::Event* aKeyEvent);
|
void DropDownToggleKey(mozilla::dom::Event* aKeyEvent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -378,6 +384,7 @@ protected:
|
|||||||
bool HandleListSelection(mozilla::dom::Event * aDOMEvent,
|
bool HandleListSelection(mozilla::dom::Event * aDOMEvent,
|
||||||
int32_t selectedIndex);
|
int32_t selectedIndex);
|
||||||
void InitSelectionRange(int32_t aClickedIndex);
|
void InitSelectionRange(int32_t aClickedIndex);
|
||||||
|
MOZ_CAN_RUN_SCRIPT
|
||||||
void PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode,
|
void PostHandleKeyEvent(int32_t aNewIndex, uint32_t aCharCode,
|
||||||
bool aIsShift, bool aIsControlOrMeta);
|
bool aIsShift, bool aIsControlOrMeta);
|
||||||
|
|
||||||
|
@ -160,7 +160,8 @@ PromptFactory.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_dispatchEvents: function(aElement) {
|
_dispatchEvents: function(aElement) {
|
||||||
// Fire both "input" and "change" events for <select> and <input>.
|
// Fire both "input" and "change" events for <select> and <input> for
|
||||||
|
// date/time.
|
||||||
aElement.dispatchEvent(new aElement.ownerGlobal.Event("input", { bubbles: true }));
|
aElement.dispatchEvent(new aElement.ownerGlobal.Event("input", { bubbles: true }));
|
||||||
aElement.dispatchEvent(new aElement.ownerGlobal.Event("change", { bubbles: true }));
|
aElement.dispatchEvent(new aElement.ownerGlobal.Event("change", { bubbles: true }));
|
||||||
},
|
},
|
||||||
|
@ -615,8 +615,13 @@ class AccessibilityTest : BaseSessionTest() {
|
|||||||
arrayOf("document", "$('#iframe').contentDocument").map { doc ->
|
arrayOf("document", "$('#iframe').contentDocument").map { doc ->
|
||||||
mainSession.evaluateJS("""new Promise(resolve =>
|
mainSession.evaluateJS("""new Promise(resolve =>
|
||||||
$doc.querySelector('${entry.key}').addEventListener(
|
$doc.querySelector('${entry.key}').addEventListener(
|
||||||
'input', event => resolve([event.target.value, '${entry.value}']),
|
'input', event => {
|
||||||
{ once: true }))""").asJSPromise()
|
let eventInterface =
|
||||||
|
event instanceof InputEvent ? "InputEvent" :
|
||||||
|
event instanceof UIEvent ? "UIEvent" :
|
||||||
|
event instanceof Event ? "Event" : "Unknown";
|
||||||
|
resolve([event.target.value, '${entry.value}', eventInterface]);
|
||||||
|
}, { once: true }))""").asJSPromise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -665,8 +670,9 @@ class AccessibilityTest : BaseSessionTest() {
|
|||||||
autoFillChild(View.NO_ID, createNodeInfo(View.NO_ID))
|
autoFillChild(View.NO_ID, createNodeInfo(View.NO_ID))
|
||||||
|
|
||||||
// Wait on the promises and check for correct values.
|
// Wait on the promises and check for correct values.
|
||||||
for ((actual, expected) in promises.map { it.value.asJSList<String>() }) {
|
for ((actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
|
||||||
assertThat("Auto-filled value must match", actual, equalTo(expected))
|
assertThat("Auto-filled value must match", actual, equalTo(expected))
|
||||||
|
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,8 +269,13 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||||||
arrayOf("document", "$('#iframe').contentDocument").map { doc ->
|
arrayOf("document", "$('#iframe').contentDocument").map { doc ->
|
||||||
mainSession.evaluateJS("""new Promise(resolve =>
|
mainSession.evaluateJS("""new Promise(resolve =>
|
||||||
$doc.querySelector('${entry.key}').addEventListener(
|
$doc.querySelector('${entry.key}').addEventListener(
|
||||||
'input', event => resolve([event.target.value, '${entry.value}']),
|
'input', event => {
|
||||||
{ once: true }))""").asJSPromise()
|
let eventInterface =
|
||||||
|
event instanceof InputEvent ? "InputEvent" :
|
||||||
|
event instanceof UIEvent ? "UIEvent" :
|
||||||
|
event instanceof Event ? "Event" : "Unknown";
|
||||||
|
resolve([event.target.value, '${entry.value}', eventInterface]);
|
||||||
|
}, { once: true }))""").asJSPromise()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,8 +353,9 @@ class ContentDelegateTest : BaseSessionTest() {
|
|||||||
mainSession.textInput.autofill(autoFillValues)
|
mainSession.textInput.autofill(autoFillValues)
|
||||||
|
|
||||||
// Wait on the promises and check for correct values.
|
// Wait on the promises and check for correct values.
|
||||||
for ((actual, expected) in promises.map { it.value.asJSList<String>() }) {
|
for ((actual, expected, eventInterface) in promises.map { it.value.asJSList<String>() }) {
|
||||||
assertThat("Auto-filled value must match", actual, equalTo(expected))
|
assertThat("Auto-filled value must match", actual, equalTo(expected))
|
||||||
|
assertThat("input event should be dispatched with InputEvent interface", eventInterface, equalTo("InputEvent"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,14 +127,7 @@ class GeckoViewAutoFill {
|
|||||||
|
|
||||||
if (element instanceof window.HTMLInputElement &&
|
if (element instanceof window.HTMLInputElement &&
|
||||||
!element.disabled && element.parentElement) {
|
!element.disabled && element.parentElement) {
|
||||||
element.value = value;
|
element.setUserInput(value);
|
||||||
|
|
||||||
// Fire both "input" and "change" events.
|
|
||||||
element.dispatchEvent(new element.ownerGlobal.Event(
|
|
||||||
"input", { bubbles: true }));
|
|
||||||
element.dispatchEvent(new element.ownerGlobal.Event(
|
|
||||||
"change", { bubbles: true }));
|
|
||||||
|
|
||||||
if (winUtils && element.value === value) {
|
if (winUtils && element.value === value) {
|
||||||
// Add highlighting for autofilled fields.
|
// Add highlighting for autofilled fields.
|
||||||
winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
|
winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user