<!DOCTYPE HTML><html><head><metacharset="utf-8"><title>Test pasting table rows</title><scriptsrc="/tests/SimpleTest/SimpleTest.js"></script><scriptsrc="/tests/SimpleTest/EventUtils.js"></script><linkrel="stylesheet"href="/tests/SimpleTest/test.css"/><style>/** * A small font-size, so that the loaded document fits on the screens of all * test devices. */*{font-size:8px;}/** * Helps fitting the tables on the screens of all test devices. */div[class="tableContainer"]{display:inline-block;}</style><script>constkEditabilityModeContenteditable="contenteditable";constkEditabilityModeDesignMode="designMode";// All column names of the test-tables used below.constkColumns=["c1","c2","c3"];// Ctrl+click on table cells to select them.constkSelectionModeClickSelection="click-selection";// Click and drag from the first given row to the end of the last given row.constkSelectionModeDragSelection="drag-selection";constkTableTagName="TABLE";constkTbodyTagName="TBODY";constkTheadTagName="THEAD";constkTfootTagName="TFOOT";constkInputEventType="input";constkInputEventInputTypeInsertFromPaste="insertFromPaste";// Where a table is pasted to in the test.constkTargetElementId="targetElement";/** * @param aTableName see Test::constructor::aTableName. * @param aRowsInTable see Test::constructor::aRowsInTable. * @return an array of elements of aRowsInTable. */functionFilterRowsWithParentTag(aTableName,aRowsInTable,aTagName){returnaRowsInTable.filter(rowName=>document.getElementById(aTableName+rowName).parentElement.tagName==aTagName);}/** * Tables used with this class are required to: * - have ids of the following form for each table cell: <tableName><rowName><column>. Where <column> has to be one of `kColumns`. - have exactly `kColumns.length` columns per row. - have an id of the form <tableName><rowName> for each table row. */classTest{/** * @param aTableName indicates which table to operate on. * @param aRowsInTable an array of row names. Ordered from top to bottom. * @param aEditabilityMode `kEditabilityModeContenteditable` or * `kEditabilityModeDesignMode`. * @param aSelectionMode `kSelectionModeClickSelection` or * `kSelectionModeDragSelection`. */constructor(aTableName,aRowsInTable,aEditabilityMode,aSelectionMode){ok(aEditabilityMode==kEditabilityModeContenteditable||aEditabilityMode==kEditabilityModeDesignMode,"Editablity mode is valid.");ok(aSelectionMode==kSelectionModeClickSelection||aSelectionMode==kSelectionModeDragSelection,"Selection mode is valid.");this._tableName=aTableName;this._rowsInTable=aRowsInTable;this._editabilityMode=aEditabilityMode;this._selectionMode=aSelectionMode;this._innerHTMLOfTargetBeforeTestRun=document.getElementById(kTargetElementId).innerHTML;if(this._editabilityMode==kEditabilityModeDesignMode){this._removeContenteditableAttributeOfTarget();document.designMode="on";}SimpleTest.info("Constructed the test ("+this._toString()+").");}/** * Call `_restoreStateOfDocumentBeforeRun` afterwards. */async_run(){// Generate the expected pasted HTML before pasting the clipboard's// content, because that may duplicate ids, hence leading to creating// a wrong expectation string.constexpectedPastedHTML=this._createExpectedOuterHTMLOfTable();if(this._selectionMode==kSelectionModeDragSelection){this._dragSelectAllCellsInRowsOfTable();}else{this._clickSelectAllCellsInRowsOfTable();}awaitthis._copyToClipboard(expectedPastedHTML);this._pasteToTargetElement();consttargetElement=document.getElementById(kTargetElementId);is(targetElement.children.length,1,"Target element has exactly one child.");is(targetElement.children[0]?.tagName,kTableTagName,"Target element has a table child.");// Linebreaks and whitespace after tags are irrelevant, hence stripping// them.is(SimpleTest.stripLinebreaksAndWhitespaceAfterTags(targetElement.children[0]?.outerHTML),expectedPastedHTML,"Pasted table ("+this._toString()+") has expected outerHTML.");}_restoreStateOfDocumentBeforeRun(){if(this._editabilityMode==kEditabilityModeDesignMode){document.designMode="off";this._setContenteditableAttributeOfTarget();}consttargetElement=document.getElementById(kTargetElementId);targetElement.innerHTML=this._innerHTMLOfTargetBeforeTestRun;targetElement.getBoundingClientRect();SimpleTest.info("Restored the state of the document before the test run.");}_toString(){return"table: "+this._tableName+"; row(s): "+this._rowsInTable.toString()+"; editability-mode: "+this._editabilityMode+"; selection-mode: "+this._selectionMode;}_removeContenteditableAttributeOfTarget(){consttargetElement=document.getElementById(kTargetElementId);SimpleTest.info("Removing target's 'contenteditable' attribute.");targetElement.removeAttribute("contenteditable");}_setContenteditableAttributeOfTarget(){consttargetElement=document.getElementById(kTargetElementId);SimpleTest.info("Setting 'contenteditable' attribute of target.");targetElement.setAttribute("contenteditable","");}_getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(aElementId){constouterHTML=document.getElementById(aElementId).outerHTML;returnSimpleTest.stripLinebreaksAndWhitespaceAfterTags(outerHTML);}_createExpectedOuterHTMLOfTable(){constrowsInTableHead=FilterRowsWithParentTag(this._tableName,this._rowsInTable,kTheadTagName);constrowsInTableBody=FilterRowsWithParentTag(this._tableName,this._rowsInTable,kTbodyTagName);constrowsInTableFoot=FilterRowsWithParentTag(this._tableName,this._rowsInTable,kTfootTagName);letexpectedTableOuterHTML='\<table>';if(rowsInTableHead.length){expectedTableOuterHTML+='\<thead>';rowsInTableHead.forEach(rowName=>expectedTableOuterHTML+=this._getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(this._tableName+rowName));expectedTableOuterHTML+='\</thead>';}if(rowsInTableBody.length){expectedTableOuterHTML+='\<tbody>';rowsInTableBody.forEach(rowName=>expectedTableOuterHTML+=this._getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(this._tableName+rowName));expectedTableOuterHTML+='\</tbody>';}if(rowsInTableFoot.length){expectedTableOuterHTML+='\<tfoot>';rowsInTableFoot.forEach(rowName=>expectedTableOuterHTML+=this._getOuterHTMLAndStripLinebreaksAndWhitespaceAfterTags(this._tableName+rowName));expectedTableOuterHTML+='\</tfoot>';}expectedTableOuterHTML+='\</table>';returnexpectedTableOuterHTML;}_clickSelectAllCellsInRowsOfTable(){functionsynthesizeAccelKeyAndClickAt(aElementId){constelement=document.getElementById(aElementId);synthesizeMouseAtCenter(element,{accelKey:true});}this._rowsInTable.forEach(rowName=>kColumns.forEach(column=>synthesizeAccelKeyAndClickAt(this._tableName+rowName+column)));}_dragSelectAllCellsInRowsOfTable(){constfirstColumnOfFirstRow=document.getElementById(this._tableName+this._rowsInTable[0]+kColumns[0]);constlastColumnOfLastRow=document.getElementById(this._tableName+this._rowsInTable.slice(-1)[0]+kColumns.slice(-1)[0]);synthesizeMouse(firstColumnOfFirstRow,0/* aOffsetX */,0/* aOffsetY */,{type:"mousedown"}/* aEvent */);constrectOfLastColumnOfLastRow=lastColumnOfLastRow.getBoundingClientRect();synthesizeMouse(lastColumnOfLastRow,rectOfLastColumnOfLastRow.width/* aOffsetX */,rectOfLastColumnOfLastRow.height/* aOffsetY */,{type:"mousemove"}/* aEvent */);synthesizeMouse(lastColumnOfLastRow,rectOfLastColumnOfLastRow.width/* aOffsetX */,rectOfLastColumnOfLastRow.height/* aOffsetY */,{type:"mouseup"}/* aEvent */);}/** * @return a promise. */async_copyToClipboard(aExpectedPastedHTML){constflavor="text/html";constexpectedPastedHTML=(()=>{if(navigator.platform.includes(kPlatformWindows)){// TODO: ideally, this should be factored out, see bug 1669963.// Windows wraps the pasted HTML, see// https://searchfox.org/mozilla-central/rev/8f7b017a31326515cb467e69eef1f6c965b4f00e/widget/windows/nsDataObj.cpp#1798-1805,1839-1840,1842.returnkTextHtmlPrefixClipboardDataWindows+aExpectedPastedHTML+kTextHtmlSuffixClipboardDataWindows;}returnaExpectedPastedHTML;})();functionvalidatorFn(aData){// The data's format doesn't specify whether there should be line// breaks or whitspace between tags. Hence, remove them.if(SimpleTest.stripLinebreaksAndWhitespaceAfterTags(aData)==SimpleTest.stripLinebreaksAndWhitespaceAfterTags(expectedPastedHTML)){returntrue;}info(`Waiting clipboard data: expected:\n"${SimpleTest.stripLinebreaksAndWhitespaceAfterTags(expectedPastedHTML)}"\n, but got:\n"${SimpleTest.stripLinebreaksAndWhitespaceAfterTags(aData)}"`);returnfalse;}returnSimpleTest.promiseClipboardChange(validatorFn,()=>synthesizeKey("c",{accelKey:true}/* aEvent*/),flavor);}_pasteToTargetElement(){consteditingHost=(this._editabilityMode==kEditabilityModeContenteditable)?document.getElementById(kTargetElementId):document;letinputEvent;functionhandleInputEvent(aEvent){if(aEvent.inputType==kInputEventInputTypeInsertFromPaste){editingHost.removeEventListener(kInputEventType,handleInputEvent);SimpleTest.info('Listened to an "'+kInputEventInputTypeInsertFromPaste+'" "'+kInputEventType+' event.');inputEvent=aEvent;}}editingHost.addEventListener(kInputEventType,handleInputEvent);consttargetElement=document.getElementById(kTargetElementId);synthesizeMouseAtCenter(targetElement,{});synthesizeKey("v",{accelKey:true}/* aEvent */);ok(inputEvent!=undefined,`An ${kInputEventType} whose "inputType" is ${kInputEventInputTypeInsertFromPaste} should've been fired on ${editingHost.localName}`);}}functionContainsRowWithParentTag(aTableName,aRowsInTable,aTagName){return!!FilterRowsWithParentTag(aTableName,aRowsInTable,aTagName).length;}functionDoesContainRowInTheadAndTbody(aTableName,aRowsInTable){returnContainsRowWithParentTag(aTableName,aRowsInTable,kTheadTagName)&&ContainsRowWithParentTag(aTableName,aRowsInTable,kTbodyTagName);}functionDoesContainRowInTbodyAndTfoot(aTableName,aRowsInTable){returnContainsRowWithParentTag(aTableName,aRowsInTable,kTbodyTagName)&&ContainsRowWithParentTag(aTableName,aRowsInTable,kTfootTagName);}asyncfunctionrunTests(){constkClickSelectionTests={selectionMode:kSelectionModeClickSelection,tablesToTest:["t1","t2","t3","t4","t5"],rowsToSelect:[["r1","r2","r3","r4"],["r1"],["r2","r3"],["r1","r3"],["r3","r4"],["r4"],],};constkDragSelectionTests={selectionMode:kSelectionModeDragSelection,tablesToTest:["t1","t2","t3","t4","t5"],// Only consecutive rows when drag-selecting.rowsToSelect:[["r1","r2","r3","r4"],["r1"],["r2","r3"],["r3","r4"],["r4"],],};constkTestGroups=[kClickSelectionTests,kDragSelectionTests];constkEditabilityModes=[kEditabilityModeContenteditable,kEditabilityModeDesignMode,];for(consteditabilityModeofkEditabilityModes){for(consttestGroupofkTestGroups){for(consttableNameoftestGroup.tablesToTest){for(constrowsToSelectoftestGroup.rowsToSelect){if(DoesContainRowInTheadAndTbody(tableName,rowsToSelect)||DoesContainRowInTbodyAndTfoot(tableName,rowsToSelect)){todo(false,'Rows to select ('+rowsToSelect.toString()+') contains '+' row in <tbody> and <thead> or <tfoot> of table "'+tableName+'", see bug 1667786.');continue;}consttest=newTest(tableName,rowsToSelect,editabilityMode,testGroup.selectionMode);try{awaittest._run();}catch(ex){ok(false,`Aborting the following tests due to unexpected error: ${ex.message}`);SimpleTest.finish();return;}test._restoreStateOfDocumentBeforeRun();}}}}SimpleTest.finish();}functiononLoad(){SimpleTest.waitForExplicitFinish();SimpleTest.waitForFocus(runTests);}</script></head><bodyonload="onLoad()"><pid="display"></p><h4>Test for <ahref="https://bugzilla.mozilla.org/show_bug.cgi?id=1639972">bug 1639972</a></h4><divid="content"><divclass="tableContainer">Table with <code>tbody</code> and <code>td</code>:<table><tbody><trid="t1r1"><tdid="t1r1c1">r1c1</td><tdid="t1r1c2">r1c2</td><tdid="t1r1c3">r1c3</td></tr><trid="t1r2"><tdid="t1r2c1">r2c1</td><tdid="t1r2c2">r2c2</td><tdid="t1r2c3">r2c3</td></tr><trid="t1r3"><tdid="t1r3c1">r3c1</td><tdid="t1r3c2">r3c2</td><tdid="t1r3c3">r3c3</td></tr><trid="t1r4"><tdid="t1r4c1">r4c1</td><tdid="t1r4c2">r4c2</td><tdid="t1r4c3">r4c3</td></tr></tbody></table></div><divclass="tableContainer">Table with <code>tbody</code>, <code>td</code> and <code>th</code>:<table><tbody><trid="t2r1"><thid="t2r1c1">r1c1</th><thid="t2r1c2">r1c2</th><thid="t2r1c3">r1c3</th></tr><trid="t2r2"><tdid="t2r2c1">r2c1</td><tdid="t2r2c2">r2c2</td><tdid="t2r2c3">r2c3</td></tr><trid="t2r3"><tdid="t2r3c1">r3c1</td><tdid="t2r3c2">r3c2</td><tdid="t2r3c3">r3c3</td></tr><trid="t2r4"><tdid="t2r4c1">r4c1</td><tdid="t2r4c2">r4c2</td><tdid="t2r4c3">r4c3</td></tr></tbody></table></div><divclass="tableContainer">Table with <code>thead</code>, <code>tbody</code>, <code>td</code>:<table><thead><trid="t3r1"><tdid="t3r1c1">r1c1</td><tdid="t3r1c2">r1c2</td><tdid="t3r1c3">r1c3</td></tr></thead><tbody><trid="t3r2"><tdid="t3r2c1">r2c1</td><tdid="t3r2c2">r2c2</td><tdid="t3r2c3">r2c3</td></tr><trid="t3r3"><tdid="t3r3c1">r3c1</td><tdid="t3r3c2">r3c2</td><tdid="t3r3c3">r3c3</td></tr><trid="t3r4"><tdid="t3r4c1">r4c1</td><tdid="t3r4c2">r4c2</td><tdid="t3r4c3">r4c3</td></tr></tbody></table></div><divclass="tableContainer">Table with <code>thead</code>, <code>tbody</code>, <code>td</code> and <code>th</code>:<table><thead><trid="t4r1"><thid="t4r1c1">r1c1</th><thid="t4r1c2">r1c2</th><thid="t4r1c3">r1c3</th></tr></thead><tbody><trid="t4r2"><tdid="t4r2c1">r2c1</td><tdid="t4r2c2">r2c2</td><tdid="t4r2c3">r2c3</td></tr><trid="t4r3"><tdid="t4r3c1">r3c1</td><tdid="t4r3c2">r3c2</td><tdid="t4r3c3">r3c3</td></tr><trid="t4r4"><tdid="t4r4c1">r4c1</td><tdid="t4r4c2">r4c2</td><tdid="t4r4c3">r4c3</td></tr></tbody></table></div><divclass="tableContainer">Table with <code>thead</code>,<code>tbody</code>, <code>tfoot</code>, and <code>td</code>:<table><thead><trid="t5r1"><tdid="t5r1c1">r1c1</td><tdid="t5r1c2">r1c2</td><tdid="t5r1c3">r1c3</td></tr></thead><tbody><trid="t5r2"><tdid="t5r2c1">r2c1</td><tdid="t5r2c2">r2c2</td><tdid="t5r2c3">r2c3</td></tr><trid="t5r3"><tdid="t5r3c1">r3c1</td><tdid="t5r3c2">r3c2</td><tdid="t5r3c3">r3c3</td></tr></tbody><tfoot><trid="t5r4"><tdid="t5r4c1">r4c1</td><tdid="t5r4c2">r4c2</td><tdid="t5r4c3">r4c3</td></tr></tfoot></table></div><p>Target for pasting:<divid="targetElement"contenteditable><!-- Some content so that it can be clicked on. -->X</div></p></div></html>