OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * This configuration can be used to rerun selected tests, as well | 6 * This configuration can be used to rerun selected tests, as well |
7 * as see diagnostic output from tests. It runs each test in its own | 7 * as see diagnostic output from tests. It runs each test in its own |
8 * IFrame, so the configuration consists of two parts - a 'master' | 8 * IFrame, so the configuration consists of two parts - a 'parent' |
9 * config that manages all the tests, and a 'slave' config for the | 9 * config that manages all the tests, and a 'child' config for the |
10 * IFrame that runs the individual tests. | 10 * IFrame that runs the individual tests. |
11 */ | 11 */ |
12 #library('interactive_config'); | 12 #library('interactive_config'); |
13 | 13 |
14 // TODO(gram) - add options for: remove IFrame on done/keep | 14 // TODO(gram) - add options for: remove IFrame on done/keep |
15 // IFrame for failed tests/keep IFrame for all tests. | 15 // IFrame for failed tests/keep IFrame for all tests. |
16 | 16 |
17 #import('dart:html'); | 17 #import('dart:html'); |
18 #import('dart:math'); | 18 #import('dart:math'); |
19 #import('unittest.dart'); | 19 #import('unittest.dart'); |
20 | 20 |
21 /** The messages exchanged between master and slave. */ | 21 /** The messages exchanged between parent and child. */ |
22 | 22 |
23 class _Message { | 23 class _Message { |
24 static const START = 'start'; | 24 static const START = 'start'; |
25 static const LOG = 'log'; | 25 static const LOG = 'log'; |
26 static const STACK = 'stack'; | 26 static const STACK = 'stack'; |
27 static const PASS = 'pass'; | 27 static const PASS = 'pass'; |
28 static const FAIL = 'fail'; | 28 static const FAIL = 'fail'; |
29 static const ERROR = 'error'; | 29 static const ERROR = 'error'; |
30 | 30 |
31 String messageType; | 31 String messageType; |
(...skipping 13 matching lines...) Expand all Loading... |
45 int idx2 = msg.indexOf(' ', idx); | 45 int idx2 = msg.indexOf(' ', idx); |
46 elapsed = parseInt(msg.substring(idx, idx2)); | 46 elapsed = parseInt(msg.substring(idx, idx2)); |
47 ++idx2; | 47 ++idx2; |
48 body = msg.substring(idx2); | 48 body = msg.substring(idx2); |
49 } | 49 } |
50 | 50 |
51 String toString() => text(messageType, elapsed, body); | 51 String toString() => text(messageType, elapsed, body); |
52 } | 52 } |
53 | 53 |
54 /** | 54 /** |
55 * The slave configuration that is used to run individual tests in | 55 * The child configuration that is used to run individual tests in |
56 * an IFrame and post the results back to the master. In principle | 56 * an IFrame and post the results back to the parent. In principle |
57 * this can run more than one test in the IFrame but currently only | 57 * this can run more than one test in the IFrame but currently only |
58 * one is used. | 58 * one is used. |
59 */ | 59 */ |
60 class SlaveInteractiveHtmlConfiguration extends Configuration { | 60 class ChildInteractiveHtmlConfiguration extends Configuration { |
61 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. | 61 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |
62 EventListener _onErrorClosure; | 62 EventListener _onErrorClosure; |
63 | 63 |
64 /** The window to which results must be posted. */ | 64 /** The window to which results must be posted. */ |
65 Window masterWindow; | 65 Window parentWindow; |
66 | 66 |
67 /** The time at which tests start. */ | 67 /** The time at which tests start. */ |
68 Map<int,Date> _testStarts; | 68 Map<int,Date> _testStarts; |
69 | 69 |
70 SlaveInteractiveHtmlConfiguration() : | 70 ChildInteractiveHtmlConfiguration() : |
71 _testStarts = new Map<int,Date>(); | 71 _testStarts = new Map<int,Date>(); |
72 | 72 |
73 /** Don't start running tests automatically. */ | 73 /** Don't start running tests automatically. */ |
74 get autoStart() => false; | 74 get autoStart => false; |
75 | 75 |
76 void onInit() { | 76 void onInit() { |
77 _onErrorClosure = | 77 _onErrorClosure = |
78 (e) => handleExternalError(e, '(DOM callback has errors)'); | 78 (e) => handleExternalError(e, '(DOM callback has errors)'); |
79 | 79 |
80 /** | 80 /** |
81 * The master posts a 'start' message to kick things off, | 81 * The parent posts a 'start' message to kick things off, |
82 * which is handled by this handler. It saves the master | 82 * which is handled by this handler. It saves the parent |
83 * window, gets the test ID from the query parameter in the | 83 * window, gets the test ID from the query parameter in the |
84 * IFrame URL, sets that as a solo test and starts test execution. | 84 * IFrame URL, sets that as a solo test and starts test execution. |
85 */ | 85 */ |
86 window.on.message.add((MessageEvent e) { | 86 window.on.message.add((MessageEvent e) { |
87 // Get the result, do any logging, then do a pass/fail. | 87 // Get the result, do any logging, then do a pass/fail. |
88 var m = new _Message.fromString(e.data); | 88 var m = new _Message.fromString(e.data); |
89 if (m.messageType == _Message.START) { | 89 if (m.messageType == _Message.START) { |
90 masterWindow = e.source; | 90 parentWindow = e.source; |
91 String search = window.location.search; | 91 String search = window.location.search; |
92 int pos = search.indexOf('t='); | 92 int pos = search.indexOf('t='); |
93 String ids = search.substring(pos+2); | 93 String ids = search.substring(pos+2); |
94 int id = parseInt(ids); | 94 int id = parseInt(ids); |
95 setSoloTest(id); | 95 setSoloTest(id); |
96 runTests(); | 96 runTests(); |
97 } | 97 } |
98 }); | 98 }); |
99 } | 99 } |
100 | 100 |
101 void onStart() { | 101 void onStart() { |
102 // Listen for uncaught errors. | 102 // Listen for uncaught errors. |
103 window.on.error.add(_onErrorClosure); | 103 window.on.error.add(_onErrorClosure); |
104 } | 104 } |
105 | 105 |
106 /** Record the start time of the test. */ | 106 /** Record the start time of the test. */ |
107 void onTestStart(TestCase testCase) { | 107 void onTestStart(TestCase testCase) { |
108 super.onTestStart(testCase); | 108 super.onTestStart(testCase); |
109 _testStarts[testCase.id]= new Date.now(); | 109 _testStarts[testCase.id]= new Date.now(); |
110 } | 110 } |
111 | 111 |
112 /** | 112 /** |
113 * Tests can call [log] for diagnostic output. These log | 113 * Tests can call [logMessage] for diagnostic output. These log |
114 * messages in turn get passed to this method, which adds | 114 * messages in turn get passed to this method, which adds |
115 * a timestamp and posts them back to the master window. | 115 * a timestamp and posts them back to the parent window. |
116 */ | 116 */ |
117 void logMessage(TestCase testCase, String message) { | 117 void logTestCaseMessage(TestCase testCase, String message) { |
118 int elapsed; | 118 int elapsed; |
119 if (testCase == null) { | 119 if (testCase == null) { |
120 elapsed = -1; | 120 elapsed = -1; |
121 } else { | 121 } else { |
122 Date end = new Date.now(); | 122 Date end = new Date.now(); |
123 elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; | 123 elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |
124 } | 124 } |
125 masterWindow.postMessage( | 125 parentWindow.postMessage( |
126 _Message.text(_Message.LOG, elapsed, message).toString(), '*'); | 126 _Message.text(_Message.LOG, elapsed, message).toString(), '*'); |
127 } | 127 } |
128 | 128 |
129 /** | 129 /** |
130 * Get the elapsed time for the test, anbd post the test result | 130 * Get the elapsed time for the test, anbd post the test result |
131 * back to the master window. If the test failed due to an exception | 131 * back to the parent window. If the test failed due to an exception |
132 * the stack is posted back too (before the test result). | 132 * the stack is posted back too (before the test result). |
133 */ | 133 */ |
134 void onTestResult(TestCase testCase) { | 134 void onTestResult(TestCase testCase) { |
135 super.onTestResult(testCase); | 135 super.onTestResult(testCase); |
136 Date end = new Date.now(); | 136 Date end = new Date.now(); |
137 int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; | 137 int elapsed = end.difference(_testStarts[testCase.id]).inMilliseconds; |
138 if (testCase.stackTrace != null) { | 138 if (testCase.stackTrace != null) { |
139 masterWindow.postMessage( | 139 parentWindow.postMessage( |
140 _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); | 140 _Message.text(_Message.STACK, elapsed, testCase.stackTrace), '*'); |
141 } | 141 } |
142 masterWindow.postMessage( | 142 parentWindow.postMessage( |
143 _Message.text(testCase.result, elapsed, testCase.message), '*'); | 143 _Message.text(testCase.result, elapsed, testCase.message), '*'); |
144 } | 144 } |
145 | 145 |
146 void onDone(int passed, int failed, int errors, List<TestCase> results, | 146 void onDone(int passed, int failed, int errors, List<TestCase> results, |
147 String uncaughtError) { | 147 String uncaughtError) { |
148 window.on.error.remove(_onErrorClosure); | 148 window.on.error.remove(_onErrorClosure); |
149 } | 149 } |
150 } | 150 } |
151 | 151 |
152 /** | 152 /** |
153 * The master configuration runs in the top-level window; it wraps the tests | 153 * The parent configuration runs in the top-level window; it wraps the tests |
154 * in new functions that create slave IFrames and run the real tests. | 154 * in new functions that create child IFrames and run the real tests. |
155 */ | 155 */ |
156 class MasterInteractiveHtmlConfiguration extends Configuration { | 156 class ParentInteractiveHtmlConfiguration extends Configuration { |
157 Map<int,Date> _testStarts; | 157 Map<int,Date> _testStarts; |
158 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. | 158 // TODO(rnystrom): Get rid of this if we get canonical closures for methods. |
159 EventListener _onErrorClosure; | 159 EventListener _onErrorClosure; |
160 | 160 |
161 /** The stack that was posted back from the slave, if any. */ | 161 /** The stack that was posted back from the child, if any. */ |
162 String _stack; | 162 String _stack; |
163 | 163 |
164 int _testTime; | 164 int _testTime; |
165 /** | 165 /** |
166 * Whether or not we have already wrapped the TestCase test functions | 166 * Whether or not we have already wrapped the TestCase test functions |
167 * in new closures that instead create an IFrame and get it to run the | 167 * in new closures that instead create an IFrame and get it to run the |
168 * test. | 168 * test. |
169 */ | 169 */ |
170 bool _doneWrap = false; | 170 bool _doneWrap = false; |
171 | 171 |
172 /** | 172 /** |
173 * We use this to make a single closure from _handleMessage so we | 173 * We use this to make a single closure from _handleMessage so we |
174 * can remove the handler later. | 174 * can remove the handler later. |
175 */ | 175 */ |
176 Function _messageHandler; | 176 Function _messageHandler; |
177 | 177 |
178 MasterInteractiveHtmlConfiguration() : | 178 ParentInteractiveHtmlConfiguration() : |
179 _testStarts = new Map<int,Date>(); | 179 _testStarts = new Map<int,Date>(); |
180 | 180 |
181 // We need to block until the test is done, so we make a | 181 // We need to block until the test is done, so we make a |
182 // dummy async callback that we will use to flag completion. | 182 // dummy async callback that we will use to flag completion. |
183 Function completeTest = null; | 183 Function completeTest = null; |
184 | 184 |
185 wrapTest(TestCase testCase) { | 185 wrapTest(TestCase testCase) { |
186 String baseUrl = window.location.toString(); | 186 String baseUrl = window.location.toString(); |
187 String url = '${baseUrl}?t=${testCase.id}'; | 187 String url = '${baseUrl}?t=${testCase.id}'; |
188 return () { | 188 return () { |
189 // Rebuild the slave IFrame. | 189 // Rebuild the child IFrame. |
190 Element slaveDiv = document.query('#slave'); | 190 Element childDiv = document.query('#child'); |
191 slaveDiv.nodes.clear(); | 191 childDiv.nodes.clear(); |
192 IFrameElement slave = new Element.html(""" | 192 IFrameElement child = new Element.html(""" |
193 <iframe id='slaveFrame${testCase.id}' src='$url' style='display:none'> | 193 <iframe id='childFrame${testCase.id}' src='$url' style='display:none'> |
194 </iframe>"""); | 194 </iframe>"""); |
195 slaveDiv.nodes.add(slave); | 195 childDiv.nodes.add(child); |
196 completeTest = expectAsync0((){ }); | 196 completeTest = expectAsync0((){ }); |
197 // Kick off the test when the IFrame is loaded. | 197 // Kick off the test when the IFrame is loaded. |
198 slave.on.load.add((e) { | 198 child.on.load.add((e) { |
199 slave.contentWindow.postMessage(_Message.text(_Message.START), '*'); | 199 child.contentWindow.postMessage(_Message.text(_Message.START), '*'); |
200 }); | 200 }); |
201 }; | 201 }; |
202 } | 202 } |
203 | 203 |
204 void _handleMessage(MessageEvent e) { | 204 void _handleMessage(MessageEvent e) { |
205 // Get the result, do any logging, then do a pass/fail. | 205 // Get the result, do any logging, then do a pass/fail. |
206 var msg = new _Message.fromString(e.data); | 206 var msg = new _Message.fromString(e.data); |
207 if (msg.messageType == _Message.LOG) { | 207 if (msg.messageType == _Message.LOG) { |
208 log(e.data); | 208 logMessage(e.data); |
209 } else if (msg.messageType == _Message.STACK) { | 209 } else if (msg.messageType == _Message.STACK) { |
210 _stack = msg.body; | 210 _stack = msg.body; |
211 } else { | 211 } else { |
212 _testTime = msg.elapsed; | 212 _testTime = msg.elapsed; |
213 log(_Message.text(_Message.LOG, _testTime, 'Complete')); | 213 logMessage(_Message.text(_Message.LOG, _testTime, 'Complete')); |
214 if (msg.messageType == _Message.PASS) { | 214 if (msg.messageType == _Message.PASS) { |
215 currentTestCase.pass(); | 215 currentTestCase.pass(); |
216 } else if (msg.messageType == _Message.FAIL) { | 216 } else if (msg.messageType == _Message.FAIL) { |
217 currentTestCase.fail(msg.body, _stack); | 217 currentTestCase.fail(msg.body, _stack); |
218 } else if (msg.messageType == _Message.ERROR) { | 218 } else if (msg.messageType == _Message.ERROR) { |
219 currentTestCase.error(msg.body, _stack); | 219 currentTestCase.error(msg.body, _stack); |
220 } | 220 } |
221 completeTest(); | 221 completeTest(); |
222 } | 222 } |
223 } | 223 } |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
279 <ul class='tests'> | 279 <ul class='tests'> |
280 </ul> | 280 </ul> |
281 </div>"""); | 281 </div>"""); |
282 document.query('#group-divs').nodes.add(groupDiv); | 282 document.query('#group-divs').nodes.add(groupDiv); |
283 groupDiv.query('.groupselect').on.click.add((e) { | 283 groupDiv.query('.groupselect').on.click.add((e) { |
284 var parent = document.query('#$groupId'); | 284 var parent = document.query('#$groupId'); |
285 InputElement cb = parent.query('.groupselect'); | 285 InputElement cb = parent.query('.groupselect'); |
286 var state = cb.checked; | 286 var state = cb.checked; |
287 var tests = parent.query('.tests'); | 287 var tests = parent.query('.tests'); |
288 for (Element t in tests.elements) { | 288 for (Element t in tests.elements) { |
289 cb = t.query('.testselect'); | 289 cb = t.query('.testselect') as InputElement; |
290 cb.checked = state; | 290 cb.checked = state; |
291 var testId = parseInt(t.id.substring(_testIdPrefix.length)); | 291 var testId = parseInt(t.id.substring(_testIdPrefix.length)); |
292 if (state) { | 292 if (state) { |
293 enableTest(testId); | 293 enableTest(testId); |
294 } else { | 294 } else { |
295 disableTest(testId); | 295 disableTest(testId); |
296 } | 296 } |
297 } | 297 } |
298 }); | 298 }); |
299 } | 299 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
337 } | 337 } |
338 }); | 338 }); |
339 } else { // Reset the test element. | 339 } else { // Reset the test element. |
340 testItem.classes.clear(); | 340 testItem.classes.clear(); |
341 testItem.classes.add('test-it'); | 341 testItem.classes.add('test-it'); |
342 testItem.classes.add('status-pending'); | 342 testItem.classes.add('status-pending'); |
343 testItem.query('#$_actionIdPrefix$id').innerHTML = ''; | 343 testItem.query('#$_actionIdPrefix$id').innerHTML = ''; |
344 } | 344 } |
345 } | 345 } |
346 | 346 |
347 // Actually test logging is handled by the slave, then posted | 347 // Actually test logging is handled by the child, then posted |
348 // back to the master. So here we know that the [message] argument | 348 // back to the parent. So here we know that the [message] argument |
349 // is in the format used by [_Message]. | 349 // is in the format used by [_Message]. |
350 void logMessage(TestCase testCase, String message) { | 350 void logTestCaseMessage(TestCase testCase, String message) { |
351 var msg = new _Message.fromString(message); | 351 var msg = new _Message.fromString(message); |
352 if (msg.elapsed < 0) { // No associated test case. | 352 if (msg.elapsed < 0) { // No associated test case. |
353 document.query('#otherlogs').nodes.add( | 353 document.query('#otherlogs').nodes.add( |
354 new Element.html('<p>${msg.body}</p>')); | 354 new Element.html('<p>${msg.body}</p>')); |
355 } else { | 355 } else { |
356 var actions = document.query('#$_testIdPrefix${testCase.id}'). | 356 var actions = document.query('#$_testIdPrefix${testCase.id}'). |
357 query('.test-actions'); | 357 query('.test-actions'); |
358 String elapsedText = msg.elapsed >= 0 ? "${msg.elapsed}ms" : ""; | 358 String elapsedText = msg.elapsed >= 0 ? "${msg.elapsed}ms" : ""; |
359 actions.nodes.add(new Element.html( | 359 actions.nodes.add(new Element.html( |
360 "<li style='list-style-stype:none>" | 360 "<li style='list-style-stype:none>" |
361 "<div class='timer-result'>${elapsedText}</div>" | 361 "<div class='timer-result'>${elapsedText}</div>" |
362 "<div class='test-title'>${msg.body}</div>" | 362 "<div class='test-title'>${msg.body}</div>" |
363 "</li>")); | 363 "</li>")); |
364 } | 364 } |
365 } | 365 } |
366 | 366 |
367 void onTestResult(TestCase testCase) { | 367 void onTestResult(TestCase testCase) { |
368 if (!testCase.enabled) return; | 368 if (!testCase.enabled) return; |
369 super.onTestResult(testCase); | 369 super.onTestResult(testCase); |
370 if (testCase.message != '') { | 370 if (testCase.message != '') { |
371 logMessage(testCase, _Message.text(_Message.LOG, -1, testCase.message)); | 371 logTestCaseMessage(testCase, |
| 372 _Message.text(_Message.LOG, -1, testCase.message)); |
372 } | 373 } |
373 int id = testCase.id; | 374 int id = testCase.id; |
374 var testItem = document.query('#$_testIdPrefix$id'); | 375 var testItem = document.query('#$_testIdPrefix$id'); |
375 var timeSpan = testItem.query('.test-timer-result'); | 376 var timeSpan = testItem.query('.test-timer-result'); |
376 timeSpan.text = '${_testTime}ms'; | 377 timeSpan.text = '${_testTime}ms'; |
377 // Convert status into what we need for our CSS. | 378 // Convert status into what we need for our CSS. |
378 String result = 'status-error'; | 379 String result = 'status-error'; |
379 if (testCase.result == 'pass') { | 380 if (testCase.result == 'pass') { |
380 result = 'status-success'; | 381 result = 'status-success'; |
381 } else if (testCase.result == 'fail') { | 382 } else if (testCase.result == 'fail') { |
(...skipping 15 matching lines...) Expand all Loading... |
397 window.on.error.remove(_onErrorClosure); | 398 window.on.error.remove(_onErrorClosure); |
398 document.query('#busy').style.display = 'none'; | 399 document.query('#busy').style.display = 'none'; |
399 InputElement startButton = document.query('#start'); | 400 InputElement startButton = document.query('#start'); |
400 startButton.disabled = false; | 401 startButton.disabled = false; |
401 } | 402 } |
402 } | 403 } |
403 | 404 |
404 /** | 405 /** |
405 * Add the divs to the DOM if they are not present. We have a 'controls' | 406 * Add the divs to the DOM if they are not present. We have a 'controls' |
406 * div for control, 'specs' div with test results, a 'busy' div for the | 407 * div for control, 'specs' div with test results, a 'busy' div for the |
407 * animated GIF used to indicate tests are running, and a 'slave' div to | 408 * animated GIF used to indicate tests are running, and a 'child' div to |
408 * hold the iframe for the test. | 409 * hold the iframe for the test. |
409 */ | 410 */ |
410 void _prepareDom() { | 411 void _prepareDom() { |
411 if (document.query('#control') == null) { | 412 if (document.query('#control') == null) { |
412 // Use this as an opportunity for adding the CSS too. | 413 // Use this as an opportunity for adding the CSS too. |
413 // I wanted to avoid having to include a css element explicitly | 414 // I wanted to avoid having to include a css element explicitly |
414 // in the main html file. I considered moving all the styles | 415 // in the main html file. I considered moving all the styles |
415 // inline as attributes but that started getting very messy, | 416 // inline as attributes but that started getting very messy, |
416 // so we do it this way. | 417 // so we do it this way. |
417 document.body.nodes.add(new Element.html("<style>$_CSS</style>")); | 418 document.body.nodes.add(new Element.html("<style>$_CSS</style>")); |
(...skipping 13 matching lines...) Expand all Loading... |
431 } | 432 } |
432 if (document.query('#specs') == null) { | 433 if (document.query('#specs') == null) { |
433 document.body.nodes.add(new Element.html( | 434 document.body.nodes.add(new Element.html( |
434 "<div id='specs'><div id='group-divs'></div></div>")); | 435 "<div id='specs'><div id='group-divs'></div></div>")); |
435 } | 436 } |
436 if (document.query('#busy') == null) { | 437 if (document.query('#busy') == null) { |
437 document.body.nodes.add(new Element.html( | 438 document.body.nodes.add(new Element.html( |
438 "<div id='busy' style='display:none'><img src='googleballs.gif'>" | 439 "<div id='busy' style='display:none'><img src='googleballs.gif'>" |
439 "</img></div>")); | 440 "</img></div>")); |
440 } | 441 } |
441 if (document.query('#slave') == null) { | 442 if (document.query('#child') == null) { |
442 document.body.nodes.add(new Element.html("<div id='slave'></div>")); | 443 document.body.nodes.add(new Element.html("<div id='child'></div>")); |
443 } | 444 } |
444 } | 445 } |
445 | 446 |
446 /** | 447 /** |
447 * Allocate a Configuration. We allocate either a master or | 448 * Allocate a Configuration. We allocate either a parent or |
448 * slave, depedning on whether the URL has a search part. | 449 * child, depedning on whether the URL has a search part. |
449 */ | 450 */ |
450 void useInteractiveHtmlConfiguration() { | 451 void useInteractiveHtmlConfiguration() { |
451 if (window.location.search == '') { // This is the master. | 452 if (window.location.search == '') { // This is the parent. |
452 _prepareDom(); | 453 _prepareDom(); |
453 configure(new MasterInteractiveHtmlConfiguration()); | 454 configure(new ParentInteractiveHtmlConfiguration()); |
454 } else { | 455 } else { |
455 configure(new SlaveInteractiveHtmlConfiguration()); | 456 configure(new ChildInteractiveHtmlConfiguration()); |
456 } | 457 } |
457 } | 458 } |
458 | 459 |
459 String _CSS = """ | 460 String _CSS = """ |
460 body { | 461 body { |
461 font-family: Arial, sans-serif; | 462 font-family: Arial, sans-serif; |
462 margin: 0; | 463 margin: 0; |
463 font-size: 14px; | 464 font-size: 14px; |
464 } | 465 } |
465 | 466 |
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
648 display: block; | 649 display: block; |
649 list-style-type: disc; | 650 list-style-type: disc; |
650 -webkit-margin-before: 1em; | 651 -webkit-margin-before: 1em; |
651 -webkit-margin-after: 1em; | 652 -webkit-margin-after: 1em; |
652 -webkit-margin-start: 0px; | 653 -webkit-margin-start: 0px; |
653 -webkit-margin-end: 0px; | 654 -webkit-margin-end: 0px; |
654 -webkit-padding-start: 40px; | 655 -webkit-padding-start: 40px; |
655 } | 656 } |
656 | 657 |
657 """; | 658 """; |
OLD | NEW |