Changeset 78565 in webkit
- Timestamp:
- Feb 15, 2011 8:14:47 AM (13 years ago)
- Location:
- trunk/LayoutTests
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r78562 r78565 1 2011-02-15 Benjamin Kalman <kalman@chromium.org> 2 3 Reviewed by Ryosuke Niwa. 4 5 Refactor the extend-selection tests to be clearer what they're doing 6 https://bugs.webkit.org/show_bug.cgi?id=54429 7 8 This is essentially a cleanup to extend-selection.js and propagation of those changes to the affected tests. 9 10 * editing/selection/extend-selection-character.html: 11 * editing/selection/extend-selection-enclosing-block.html: 12 * editing/selection/extend-selection-home-end.html: 13 * editing/selection/extend-selection-word.html: 14 * editing/selection/resources/extend-selection.js: 15 (getSerializedSelection): 16 (extendSelectionWithinBlock): 17 (extendSelectionToEnd): 18 (fold): 19 (logMismatchingPositions): 20 (extendAndLogSelection): 21 (extendAndLogSelectionWithinBlock): 22 (extendAndLogSelectionToEnd): 23 (runSelectionTestsWithGranularity): 24 (getTestNodeContainer): 25 (createNode): 26 (createCharAndWordNodes): 27 (createEnclosingBlockNodes): 28 (createHomeEndNodes): 29 (createAllNodes): 30 (.window.onload): 31 1 32 2011-02-15 Philippe Normand <pnormand@igalia.com> 2 33 -
trunk/LayoutTests/editing/selection/extend-selection-character.html
r72482 r78565 9 9 <pre id="console"></pre> 10 10 <script> 11 12 if (window.layoutTestController) { 13 layoutTestController.dumpAsText(); 14 15 var tests = createTestNodes("char-and-word"); 16 var sel = getSelection(); 17 18 log("\n\n\nExtending by character\n"); 19 testExtendingSelection(tests, sel, "character", 0); 20 } 21 11 log("\n\n\nExtending by character\n"); 12 runSelectionTestsWithGranularity(createCharAndWordNodes(), "character"); 22 13 </script> 23 14 </body> -
trunk/LayoutTests/editing/selection/extend-selection-enclosing-block.html
r72482 r78565 9 9 <pre id="console"></pre> 10 10 <script> 11 function runSelectionTestsWithGranularityForEnclosingBlock(testNodes, granularity) 12 { 13 for (var i = 0; i < testNodes.length; ++i) { 14 getSelection().setPosition(testNodes[i], 0); 11 15 12 function testExtendingSelectionForEnclosingBlock(tests, sel, granularity) 13 { 14 for (var i = 0; i < tests.length; ++i) { 15 log("Test " + (i + 1) + ", LTR:\n Extending right: "); 16 sel.setPosition(tests[i], 0); 17 var ltrRightPos = extendingSelection(sel, "right", granularity, 1); 16 log("Test " + (i + 1) + ", LTR:\n"); 17 log(" Extending right: "); 18 extendAndLogSelectionWithinBlock("right", granularity); 18 19 19 20 log(" Extending left: "); 20 var ltrLeftPos = extendingSelection(sel, "left", granularity, 1);21 extendAndLogSelectionWithinBlock("left", granularity); 21 22 } 22 23 } 23 24 24 if (window.layoutTestController) { 25 layoutTestController.dumpAsText();25 log("\n\n\nExtending by character\n"); 26 runSelectionTestsWithGranularityForEnclosingBlock(createEnclosingBlockNodes(), "character"); 26 27 27 tests = createTestNodes("enclosing-block"); 28 sel = getSelection(); 29 30 log("\n\n\nExtending by character\n"); 31 testExtendingSelectionForEnclosingBlock(tests, sel, "character", 1); 32 log("\n\n\n\n\nExtending by word\n"); 33 testExtendingSelectionForEnclosingBlock(tests, sel, "word", 1); 34 } 35 28 log("\n\n\n\n\nExtending by word\n"); 29 runSelectionTestsWithGranularityForEnclosingBlock(createEnclosingBlockNodes(), "word"); 36 30 </script> 37 31 </body> -
trunk/LayoutTests/editing/selection/extend-selection-home-end.html
r72482 r78565 9 9 <pre id="console"></pre> 10 10 <script> 11 function testExtendingLineBoundary(testNodes) 12 { 13 function extendToEndAndBack(node) 14 { 15 getSelection().setPosition(node, 0); 16 log(" Extending forward: "); 17 extendAndLogSelectionToEnd("forward", "lineBoundary"); 18 log(" Extending backward: "); 19 extendAndLogSelectionToEnd("backward", "lineBoundary"); 20 } 11 21 12 function testExtendingLineBoundary(tests, sel) 13 { 14 for (var i = 0; i < tests.length; ++i) { 15 tests[i].style.direction = "ltr"; 16 log("Test " + (i + 1) + ", LTR:\n Extending forward: "); 17 sel.setPosition(tests[i], 0); 18 var ltrRightPos = extendingSelection(sel, "forward", "lineBoundary", 0); 22 for (var i = 0; i < testNodes.length; ++i) { 23 var node = testNodes[i]; 19 24 20 log(" Extending backward: "); 21 var ltrLeftPos = extendingSelection(sel, "backward", "lineBoundary", 0); 25 log("Test " + (i + 1) + ", LTR:\n"); 26 node.style.direction = "ltr"; 27 extendToEndAndBack(node); 22 28 23 tests[i].style.direction = "rtl"; 24 log("Test " + (i + 1) + ", RTL:\n Extending forward: "); 25 sel.setPosition(tests[i], 0); 26 var ltrRightPos = extendingSelection(sel, "forward", "lineBoundary", 0); 27 28 log(" Extending backward: "); 29 var ltrLeftPos = extendingSelection(sel, "backward", "lineBoundary", 0); 29 log("Test " + (i + 1) + ", RTL:\n"); 30 node.style.direction = "rtl"; 31 extendToEndAndBack(node); 30 32 } 31 33 } 32 34 33 if (window.layoutTestController) { 34 layoutTestController.dumpAsText(); 35 36 tests = createTestNodes(); // all 37 var sel = getSelection(); 38 log("\n\n\nExtending by lineBoundary\n"); 39 testExtendingLineBoundary(tests, sel); 40 } 41 35 log("\n\n\nExtending by lineBoundary\n"); 36 testExtendingLineBoundary(createAllNodes()); 42 37 </script> 43 38 </body> -
trunk/LayoutTests/editing/selection/extend-selection-word.html
r72482 r78565 9 9 <pre id="console"></pre> 10 10 <script> 11 12 if (window.layoutTestController) { 13 layoutTestController.dumpAsText(); 14 15 var tests = createTestNodes('char-and-word'); 16 var sel = getSelection(); 17 18 log("\n\n\n\n\nExtending by word\n"); 19 testExtendingSelection(tests, sel, "word", 0); 20 } 21 11 log("\n\n\n\n\nExtending by word\n"); 12 runSelectionTestsWithGranularity(createCharAndWordNodes(), "word"); 22 13 </script> 23 14 </body> -
trunk/LayoutTests/editing/selection/resources/extend-selection.js
r74331 r78565 4 4 } 5 5 6 function positionsExtendingInDirectionForEnclosingBlock(sel, direction, granularity) 6 function getSerializedSelection() 7 { 8 return { 9 node: getSelection().extentNode, 10 begin: getSelection().baseOffset, 11 end: getSelection().extentOffset 12 }; 13 } 14 15 function extendSelectionWithinBlock(direction, granularity) 7 16 { 8 17 var positions = []; 9 var ltrNum; 10 var rtlNum; 11 if (granularity == "character") { 12 ltrNum = 5; 13 } else { 14 ltrNum = 1; 15 } 16 if (granularity == "character") { 17 rtlNum = 15; 18 } else { 19 rtlNum = 3; 20 } 21 var index = 0; 22 while (index <= ltrNum) { 23 positions.push({ node: sel.extentNode, begin: sel.baseOffset, end: sel.extentOffset }); 24 sel.modify("extend", direction, granularity); 25 ++index; 26 } 27 var antiDirection = direction; 28 if (antiDirection == 'left') { 29 antiDirection = "right"; 30 } else if (antiDirection = 'right') { 31 antiDirection = "left"; 32 } 33 34 index = 0; 35 while (index <= rtlNum) { 36 positions.push({ node: sel.extentNode, begin: sel.baseOffset, end: sel.extentOffset }); 37 sel.modify("extend", antiDirection, granularity); 38 ++index; 39 } 40 var index = 0; 41 while (index < ltrNum) { 42 positions.push({ node: sel.extentNode, begin: sel.baseOffset, end: sel.extentOffset }); 43 sel.modify("extend", direction, granularity); 44 ++index; 45 } 18 var leftIterations = (granularity === "character") ? 5 : 1; 19 var rightIterations = (granularity === "character") ? 15 : 3; 20 21 for (var index = 0; index <= leftIterations; ++index) { 22 positions.push(getSerializedSelection()); 23 getSelection().modify("extend", direction, granularity); 24 } 25 26 for (var index = 0; index <= rightIterations; ++index) { 27 positions.push(getSerializedSelection()); 28 getSelection().modify("extend", (direction === "left") ? "right" : "left", granularity); 29 } 30 31 for (var index = 0; index < leftIterations; ++index) { 32 positions.push(getSerializedSelection()); 33 getSelection().modify("extend", direction, granularity); 34 } 35 46 36 return positions; 47 37 } 48 38 49 50 function positionsExtendingInDirection(sel, direction, granularity) 39 function extendSelectionToEnd(direction, granularity) 51 40 { 52 41 var positions = []; 53 54 while (true) { 55 positions.push({ node: sel.extentNode, begin: sel.baseOffset, end: sel.extentOffset }); 56 sel.modify("extend", direction, granularity); 57 if (positions[positions.length - 1].node == sel.extentNode && positions[positions.length - 1].end == sel.extentOffset) 58 break; 59 }; 42 do { 43 var position = getSerializedSelection(); 44 positions.push(position); 45 getSelection().modify("extend", direction, granularity); 46 } while (position.node !== getSerializedSelection().node || position.end !== getSerializedSelection().end); 60 47 return positions; 61 48 } … … 69 56 char -= 0x058f; 70 57 else if (char == 10) { 71 result += 58 result +="\\n"; 72 59 continue; 73 60 } … … 92 79 } 93 80 94 function checkReverseOrder(inputPositions, inputReversePositions) 95 { 96 var positions = inputPositions.slice(); 97 var reversePositions = inputReversePositions.slice(); 98 var mismatch = (positions.length != reversePositions.length); 99 if (mismatch) 100 log("WARNING: positions should be the same, but the length are not the same " + positions.length + " vs. " + reversePositions.length + "\n"); 101 while (!mismatch) { 102 var pos = positions.pop(); 103 if (!pos) 104 break; 105 var reversePos = reversePositions.shift(); 106 if (pos.node != reversePos.node) { 107 mismatch = true; 108 log("WARNING: positions should be the reverse, but node are not the reverse\n"); 109 } 110 if (pos.begin != reversePos.begin) { 111 mismatch = true; 112 log("WARNING: positions should be the same, but begin are not " + pos.begin + " vs. " + reversePos.begin + "\n"); 113 } 114 if (pos.end != reversePos.end) { 115 mismatch = true; 116 log("WARNING: positions should be the same, but end are not " + pos.end + " vs. " + reversePos.end + "\n"); 117 } 118 } 119 } 120 121 122 function checkSameOrder(inputPositions, inputSamePositions) 123 { 124 var positions = inputPositions.slice(); 125 var samePositions = inputSamePositions.slice(); 126 var mismatch = positions.length != samePositions.length; 127 if (mismatch) 81 function logMismatchingPositions(positions, comparisonPositions) 82 { 83 if (positions.length !== comparisonPositions.length) { 128 84 log("WARNING: positions should be the same, but the length are not the same " + positions.length + " vs. " + samePositions.length + "\n"); 129 while (!mismatch) { 130 var pos = positions.pop(); 131 if (!pos) 132 break; 133 var samePos = samePositions.pop(); 134 if (pos.node != samePos.node) { 135 mismatch = true; 85 return; 86 } 87 88 for (var i = 0; i < positions.length; ++i) { 89 var pos = positions[i]; 90 var comparison = comparisonPositions[i]; 91 92 if (pos.node !== comparison.node) 136 93 log("WARNING: positions should be the same, but node are not the same\n"); 137 } 138 if (pos.begin != samePos.begin) { 139 mismatch = true; 140 log("WARNING: positions should be the same, but begin are not the same " + pos.begin + " vs. " + samePos.begin + "\n"); 141 } 142 if (pos.end != samePos.end) { 143 mismatch = true; 144 log("WARNING: positions should be the same, but end are not the same " + pos.end + " vs. " + samePos.end + "\n"); 145 } 146 } 147 } 148 149 150 function extendingSelection(sel, direction, granularity, option) 151 { 152 var positions; 153 if (option == 0) { 154 positions = positionsExtendingInDirection(sel, direction, granularity); 155 } else { 156 positions = positionsExtendingInDirectionForEnclosingBlock(sel, direction, granularity); 157 } 94 if (pos.begin !== comparison.begin) 95 log("WARNING: positions should be the same, but begin are not the same " + pos.begin + " vs. " + comparison.begin + "\n"); 96 if (pos.end !== comparison.end) 97 log("WARNING: positions should be the same, but end are not the same " + pos.end + " vs. " + comparison.end + "\n"); 98 } 99 } 100 101 function extendAndLogSelection(functionToExtendSelection, direction, granularity) 102 { 103 var positions = functionToExtendSelection(direction, granularity); 158 104 logPositions(positions); 159 105 log("\n"); … … 161 107 } 162 108 163 function testExtendingSelection(tests, sel, granularity) 164 { 165 for (var i = 0; i < tests.length; ++i) { 166 tests[i].style.direction = "ltr"; 167 log("Test " + (i + 1) + ", LTR:\n Extending right: "); 168 sel.setPosition(tests[i], 0); 169 var ltrRightPos = extendingSelection(sel, "right", granularity, 0); 109 function extendAndLogSelectionWithinBlock(direction, granularity) 110 { 111 return extendAndLogSelection(extendSelectionWithinBlock, direction, granularity); 112 } 113 114 function extendAndLogSelectionToEnd(direction, granularity) 115 { 116 return extendAndLogSelection(extendSelectionToEnd, direction, granularity); 117 } 118 119 function runSelectionTestsWithGranularity(testNodes, granularity) 120 { 121 for (var i = 0; i < testNodes.length; ++i) { 122 var testNode = testNodes[i]; 123 124 testNode.style.direction = "ltr"; 125 log("Test " + (i + 1) + ", LTR:\n"); 126 log(" Extending right: "); 127 getSelection().setPosition(testNode); 128 var ltrRightPos = extendAndLogSelectionToEnd("right", granularity); 170 129 171 130 log(" Extending left: "); 172 var ltrLeftPos = extend ingSelection(sel, "left", granularity, 0);131 var ltrLeftPos = extendAndLogSelectionToEnd("left", granularity); 173 132 174 133 log(" Extending forward: "); 175 sel.setPosition(tests[i], 0);176 var ltrForwardPos = extend ingSelection(sel, "forward", granularity, 0);134 getSelection().setPosition(testNode); 135 var ltrForwardPos = extendAndLogSelectionToEnd("forward", granularity); 177 136 178 137 log(" Extending backward: "); 179 var ltrBackwardPos = extendingSelection(sel, "backward", granularity, 0); 180 181 tests[i].style.direction = "rtl"; 182 183 log("Test " + (i + 1) + ", RTL:\n Extending left: "); 184 sel.setPosition(tests[i], 0); 185 var rtlLeftPos = extendingSelection(sel, "left", granularity, 0); 138 var ltrBackwardPos = extendAndLogSelectionToEnd("backward", granularity); 139 140 testNode.style.direction = "rtl"; 141 142 log("Test " + (i + 1) + ", RTL:\n"); 143 log(" Extending left: "); 144 getSelection().setPosition(testNode); 145 var rtlLeftPos = extendAndLogSelectionToEnd("left", granularity); 186 146 187 147 log(" Extending right: "); 188 var rtlRightPos = extend ingSelection(sel, "right", granularity, 0);148 var rtlRightPos = extendAndLogSelectionToEnd("right", granularity); 189 149 190 150 log(" Extending forward: "); 191 sel.setPosition(tests[i], 0);192 var rtlForwardPos = extend ingSelection(sel, "forward", granularity, 0);151 getSelection().setPosition(testNode); 152 var rtlForwardPos = extendAndLogSelectionToEnd("forward", granularity); 193 153 194 154 log(" Extending backward: "); 195 var rtlBackwardPos = extend ingSelection(sel, "backward", granularity, 0);155 var rtlBackwardPos = extendAndLogSelectionToEnd("backward", granularity); 196 156 197 157 // validations 198 158 log("\n\n validating ltrRight and ltrLeft\n"); 199 159 if (granularity == "character") 200 checkReverseOrder(ltrRightPos, ltrLeftPos);160 logMismatchingPositions(ltrRightPos, ltrLeftPos.slice().reverse()); 201 161 // Order might not be reversed for extending by word because the 1-point shift by space. 202 162 203 163 log(" validating ltrRight and ltrForward\n"); 204 checkSameOrder(ltrRightPos, ltrForwardPos);164 logMismatchingPositions(ltrRightPos, ltrForwardPos); 205 165 log(" validating ltrForward and rtlForward\n"); 206 checkSameOrder(ltrForwardPos, rtlForwardPos);166 logMismatchingPositions(ltrForwardPos, rtlForwardPos); 207 167 log(" validating ltrLeft and ltrBackward\n"); 208 checkSameOrder(ltrLeftPos, ltrBackwardPos);168 logMismatchingPositions(ltrLeftPos, ltrBackwardPos); 209 169 log(" validating ltrBackward and rtlBackward\n"); 210 checkSameOrder(ltrBackwardPos, rtlBackwardPos);170 logMismatchingPositions(ltrBackwardPos, rtlBackwardPos); 211 171 log(" validating ltrRight and rtlLeft\n"); 212 checkSameOrder(ltrRightPos, rtlLeftPos);172 logMismatchingPositions(ltrRightPos, rtlLeftPos); 213 173 log(" validating ltrLeft and rtlRight\n"); 214 checkSameOrder(ltrLeftPos, rtlRightPos);174 logMismatchingPositions(ltrLeftPos, rtlRightPos); 215 175 log("\n\n"); 216 176 } 217 177 } 218 178 219 var data = [ 220 ['char-and-word', null, '\nabc אבג xyz דהו def\n'], 221 ['char-and-word', null, '\nאבג xyz דהו def זחט\n'], 222 ['char-and-word', null, '\nאבג דהו אבג\n'], 223 ['char-and-word', null, '\nabc efd dabeb\n'], 224 ['char-and-word', null, 'Lorem <span style="direction: rtl">ipsum dolor sit</span> amet'], 225 ['char-and-word', null, 'Lorem <span dir="rtl">ipsum dolor sit</span> amet'], 226 ['char-and-word', null, 'Lorem <span style="direction: ltr">ipsum dolor sit</span> amet'], 227 ['char-and-word', null, 'Lorem <span dir="ltr">ipsum dolor sit</span> amet'], 228 ['enclosing-block', null, 'Lorem <div dir="rtl">ipsum dolor sit</div> amett'], 229 ['home-end', null, 'Lorem <span style="direction: ltr">ipsum dolor sit</span> amet'], 230 ['home-end', null, 'Lorem <span style="direction: ltr">ipsum dolor<div > just a test</div> sit</span> amet'], 231 ['home-end', null, 'Lorem <span dir="ltr">ipsum dolor sit</span> amet'], 232 ['home-end', null, 'Lorem <div dir="ltr">ipsum dolor sit</div> amet'], 233 ['home-end', null, '\n Just\n <span>testing רק</span>\n בודק\n'], 234 ['home-end', null, '\n Just\n <span>testing what</span>\n ever\n'], 235 ['home-end', null, 'car means אבג.'], 236 ['home-end', null, '‫car דהו אבג.‬'], 237 ['home-end', null, 'he said "‫car דהו אבג‬."'], 238 ['home-end', null, 'זחט יךכ לםמ \'‪he said "‫car דהו אבג‬"‬\'?'], 239 ['home-end', null, 'אבג abc דהו<br />edf זחט abrebg'], 240 ['home-end', 'line-break:before-white-space; width:5em', 'abcdefg abcdefg abcdefg a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg '], 241 ['home-end', 'line-break:after-white-space; width:5em', 'abcdefg abcdefg abcdefg a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg '] 242 ]; 243 244 function createTestNodes(name) { 245 var nodes = []; 246 for (var i = 0; i < data.length; i++) { 247 if (name && data[i][0] != name) 248 continue; 249 var style = data[i][1]; 250 var text = data[i][2]; 251 var div = document.createElement('div'); 252 div.contentEditable = true; 253 if (style) 254 div.setAttribute('style', style); 255 div.innerHTML = text; 256 document.body.insertBefore(div, document.getElementById('console')); 257 nodes.push(div); 258 } 259 179 function getTestNodeContainer() 180 { 181 var tests = document.getElementById("tests"); 182 if (!tests) { 183 tests = document.createElement("div"); 184 tests.id = "tests"; 185 document.body.insertBefore(tests, document.getElementById("console")); 186 } 187 return tests; 188 } 189 190 function createNode(content, style) 191 { 192 var node = document.createElement("div"); 193 node.innerHTML = content; 194 node.contentEditable = true; 195 if (style) 196 node.setAttribute("style", style); 197 getTestNodeContainer().appendChild(node); 198 return node; 199 } 200 201 function createCharAndWordNodes() 202 { 203 return [ 204 createNode('\nabc אבג xyz דהו def\n'), 205 createNode('\nאבג xyz דהו def זחט\n'), 206 createNode('\nאבג דהו אבג\n'), 207 createNode('\nabc efd dabeb\n'), 208 createNode('Lorem <span style="direction: rtl">ipsum dolor sit</span> amet'), 209 createNode('Lorem <span dir="rtl">ipsum dolor sit</span> amet'), 210 createNode('Lorem <span style="direction: ltr">ipsum dolor sit</span> amet'), 211 createNode('Lorem <span dir="ltr">ipsum dolor sit</span> amet') 212 ]; 213 } 214 215 function createEnclosingBlockNodes() 216 { 217 return [ 218 createNode('Lorem <div dir="rtl">ipsum dolor sit</div> amett') 219 ]; 220 } 221 222 function createHomeEndNodes() 223 { 224 return [ 225 createNode('Lorem <span style="direction: ltr">ipsum dolor sit</span> amet'), 226 createNode('Lorem <span style="direction: ltr">ipsum dolor<div > just a test</div> sit</span> amet'), 227 createNode('Lorem <span dir="ltr">ipsum dolor sit</span> amet'), 228 createNode('Lorem <div dir="ltr">ipsum dolor sit</div> amet'), 229 createNode('\n Just\n <span>testing רק</span>\n בודק\n'), 230 createNode('\n Just\n <span>testing what</span>\n ever\n'), 231 createNode('car means אבג.'), 232 createNode('‫car דהו אבג.‬'), 233 createNode('he said "‫car דהו אבג‬."'), 234 createNode('זחט יךכ לםמ \'‪he said ' + 235 '"‫car דהו אבג‬"‬\'?'), 236 createNode('אבג abc דהו<br />edf זחט abrebg'), 237 createNode('abcdefg abcdefg abcdefg a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg ', 238 'line-break:before-white-space; width:5em'), 239 createNode('abcdefg abcdefg abcdefg a abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg abcdefg ', 240 'line-break:after-white-space; width:5em') 241 ]; 242 } 243 244 function createAllNodes() 245 { 246 return createCharAndWordNodes().concat( 247 createEnclosingBlockNodes()).concat( 248 createHomeEndNodes()); 249 } 250 251 if (window.layoutTestController) { 252 var originalOnload = window.onload; 260 253 window.onload = function() { 261 for (var i = 0; i < nodes.length; i++) 262 nodes[i].parentNode.removeChild(nodes[i]); 263 } 264 265 return nodes; 266 } 267 254 if (originalOnload) 255 originalOnload(); 256 layoutTestController.dumpAsText(); 257 document.body.removeChild(getTestNodeContainer()); 258 }; 259 }
Note: See TracChangeset
for help on using the changeset viewer.