Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview using private properties isn't a Closure violation in tests. | 6 * @fileoverview using private properties isn't a Closure violation in tests. |
| 7 * @suppress {accessControls} | 7 * @suppress {accessControls} |
| 8 */ | 8 */ |
| 9 | 9 |
| 10 /** @typedef {function(!Element, !ConsoleModel.ConsoleMessage=):string} */ | |
| 11 ConsoleTestRunner.Formatter; | |
| 12 | |
| 10 /** | 13 /** |
| 11 * @param {boolean} printOriginatingCommand | 14 * @param {boolean=} printOriginatingCommand |
| 12 * @param {boolean} dumpClassNames | 15 * @param {boolean=} dumpClassNames |
| 13 * @param {function(!Element, !ConsoleModel.ConsoleMessage):string=} formatter | 16 * @param {!ConsoleTestRunner.Formatter=} formatter |
| 14 */ | 17 */ |
| 15 ConsoleTestRunner.dumpConsoleMessages = function(printOriginatingCommand, dumpCl assNames, formatter) { | 18 ConsoleTestRunner.dumpConsoleMessages = function(printOriginatingCommand, dumpCl assNames, formatter) { |
| 16 TestRunner.addResults( | 19 TestRunner.addResults( |
| 17 ConsoleTestRunner.dumpConsoleMessagesIntoArray(printOriginatingCommand, du mpClassNames, formatter)); | 20 ConsoleTestRunner.dumpConsoleMessagesIntoArray(printOriginatingCommand, du mpClassNames, formatter)); |
| 18 }; | 21 }; |
| 19 | 22 |
| 20 /** | 23 /** |
| 21 * @param {boolean} printOriginatingCommand | 24 * @param {boolean=} printOriginatingCommand |
| 22 * @param {boolean} dumpClassNames | 25 * @param {boolean=} dumpClassNames |
| 23 * @param {function(!Element, !ConsoleModel.ConsoleMessage):string=} formatter | 26 * @param {!ConsoleTestRunner.Formatter=} formatter |
| 24 * @return {!Array<string>} | 27 * @return {!Array<string>} |
| 25 */ | 28 */ |
| 26 ConsoleTestRunner.dumpConsoleMessagesIntoArray = function(printOriginatingComman d, dumpClassNames, formatter) { | 29 ConsoleTestRunner.dumpConsoleMessagesIntoArray = function(printOriginatingComman d, dumpClassNames, formatter) { |
| 27 formatter = formatter || ConsoleTestRunner.prepareConsoleMessageText; | 30 formatter = formatter || ConsoleTestRunner.prepareConsoleMessageText; |
| 28 var result = []; | 31 var result = []; |
| 29 ConsoleTestRunner.disableConsoleViewport(); | 32 ConsoleTestRunner.disableConsoleViewport(); |
| 30 var consoleView = Console.ConsoleView.instance(); | 33 var consoleView = Console.ConsoleView.instance(); |
| 31 if (consoleView._needsFullUpdate) | 34 if (consoleView._needsFullUpdate) |
| 32 consoleView._updateMessageList(); | 35 consoleView._updateMessageList(); |
| 33 var viewMessages = consoleView._visibleViewMessages; | 36 var viewMessages = consoleView._visibleViewMessages; |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 57 } | 60 } |
| 58 | 61 |
| 59 if (printOriginatingCommand && uiMessage.consoleMessage().originatingMessage ()) | 62 if (printOriginatingCommand && uiMessage.consoleMessage().originatingMessage ()) |
| 60 result.push('Originating from: ' + uiMessage.consoleMessage().originatingM essage().messageText); | 63 result.push('Originating from: ' + uiMessage.consoleMessage().originatingM essage().messageText); |
| 61 } | 64 } |
| 62 return result; | 65 return result; |
| 63 }; | 66 }; |
| 64 | 67 |
| 65 /** | 68 /** |
| 66 * @param {!Element} messageElement | 69 * @param {!Element} messageElement |
| 67 * @param {!ConsoleModel.ConsoleMessage} consoleMessage | |
| 68 * @return {string} | 70 * @return {string} |
| 69 */ | 71 */ |
| 70 ConsoleTestRunner.prepareConsoleMessageText = function(messageElement, consoleMe ssage) { | 72 ConsoleTestRunner.prepareConsoleMessageText = function(messageElement) { |
| 71 var messageText = messageElement.deepTextContent().replace(/\u200b/g, ''); | 73 var messageText = messageElement.deepTextContent().replace(/\u200b/g, ''); |
| 72 // Replace scriptIds with generic scriptId string to avoid flakiness. | 74 // Replace scriptIds with generic scriptId string to avoid flakiness. |
| 73 messageText = messageText.replace(/VM\d+/g, 'VM'); | 75 messageText = messageText.replace(/VM\d+/g, 'VM'); |
| 74 // Remove line and column of evaluate method. | 76 // Remove line and column of evaluate method. |
| 75 messageText = messageText.replace(/(at eval \(eval at evaluate) \(:\d+:\d+\)/, '$1'); | 77 messageText = messageText.replace(/(at eval \(eval at evaluate) \(:\d+:\d+\)/, '$1'); |
| 76 | 78 |
| 77 if (messageText.startsWith('Navigated to')) { | 79 if (messageText.startsWith('Navigated to')) { |
| 78 var fileName = messageText.split(' ').pop().split('/').pop(); | 80 var fileName = messageText.split(' ').pop().split('/').pop(); |
| 79 messageText = 'Navigated to ' + fileName; | 81 messageText = 'Navigated to ' + fileName; |
| 80 } | 82 } |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 136 /** | 138 /** |
| 137 * @param {number} width | 139 * @param {number} width |
| 138 * @param {number} height | 140 * @param {number} height |
| 139 */ | 141 */ |
| 140 ConsoleTestRunner.fixConsoleViewportDimensions = function(width, height) { | 142 ConsoleTestRunner.fixConsoleViewportDimensions = function(width, height) { |
| 141 var viewport = Console.ConsoleView.instance()._viewport; | 143 var viewport = Console.ConsoleView.instance()._viewport; |
| 142 viewport.element.style.width = width + 'px'; | 144 viewport.element.style.width = width + 'px'; |
| 143 viewport.element.style.height = height + 'px'; | 145 viewport.element.style.height = height + 'px'; |
| 144 viewport.element.style.position = 'absolute'; | 146 viewport.element.style.position = 'absolute'; |
| 145 viewport.invalidate(); | 147 viewport.invalidate(); |
| 146 }; | 148 }; |
| 149 | |
| 150 ConsoleTestRunner.selectMainExecutionContext = function() { | |
| 151 var executionContexts = TestRunner.runtimeModel.executionContexts(); | |
| 152 for (var context of executionContexts) { | |
| 153 if (context.isDefault) { | |
| 154 UI.context.setFlavor(SDK.ExecutionContext, context); | |
| 155 return; | |
| 156 } | |
| 157 } | |
| 158 }; | |
| 159 | |
| 160 /** | |
| 161 * @param {string} code | |
| 162 * @param {!Function=} callback | |
| 163 * @param {boolean=} dontForceMainContext | |
| 164 */ | |
| 165 ConsoleTestRunner.evaluateInConsole = function(code, callback, dontForceMainCont ext) { | |
| 166 if (!dontForceMainContext) | |
| 167 ConsoleTestRunner.selectMainExecutionContext(); | |
| 168 callback = TestRunner.safeWrap(callback); | |
| 169 | |
| 170 var consoleView = Console.ConsoleView.instance(); | |
| 171 consoleView._prompt._appendCommand(code, true); | |
| 172 ConsoleTestRunner.addConsoleViewSniffer(function(commandResult) { | |
| 173 callback(commandResult.toMessageElement().deepTextContent()); | |
| 174 }); | |
| 175 }; | |
| 176 | |
| 177 /** | |
| 178 * @param {!Function} override | |
| 179 * @param {boolean=} opt_sticky | |
| 180 */ | |
| 181 ConsoleTestRunner.addConsoleViewSniffer = function(override, opt_sticky) { | |
| 182 var sniffer = function(viewMessage) { | |
|
caseq
2017/07/12 01:04:33
why not just pass override directly? Also, perhaps
chenwilliam
2017/07/12 22:02:58
I passed the override directly.
See earlier comme
| |
| 183 override(viewMessage); | |
| 184 }; | |
| 185 | |
| 186 TestRunner.addSniffer(Console.ConsoleView.prototype, '_consoleMessageAddedForT est', sniffer, opt_sticky); | |
| 187 }; | |
| 188 | |
| 189 /** | |
| 190 * @param {string} code | |
| 191 * @param {!Function=} callback | |
| 192 * @param {boolean=} dontForceMainContext | |
| 193 */ | |
| 194 ConsoleTestRunner.evaluateInConsoleAndDump = function(code, callback, dontForceM ainContext) { | |
| 195 callback = TestRunner.safeWrap(callback); | |
|
caseq
2017/07/12 01:04:33
No need for this, you already wrap in evalueteInCo
chenwilliam
2017/07/12 22:02:58
Done.
| |
| 196 | |
| 197 /** | |
| 198 * @param {string} text | |
| 199 */ | |
| 200 function mycallback(text) { | |
| 201 text = text.replace(/\bVM\d+/g, 'VM'); | |
| 202 TestRunner.addResult(code + ' = ' + text); | |
| 203 callback(text); | |
| 204 } | |
| 205 ConsoleTestRunner.evaluateInConsole(code, mycallback, dontForceMainContext); | |
| 206 }; | |
| 207 | |
| 208 /** | |
| 209 * @return {number} | |
| 210 */ | |
| 211 ConsoleTestRunner.consoleMessagesCount = function() { | |
| 212 var consoleView = Console.ConsoleView.instance(); | |
| 213 return consoleView._consoleMessages.length; | |
| 214 }; | |
| 215 | |
| 216 /** | |
| 217 * @param {function(!Element):string} messageFormatter | |
| 218 * @param {!Element} node | |
| 219 * @return {string} | |
| 220 */ | |
| 221 ConsoleTestRunner.formatterIgnoreStackFrameUrls = function(messageFormatter, nod e) { | |
| 222 /** | |
| 223 * @param {string} string | |
| 224 */ | |
| 225 function isNotEmptyLine(string) { | |
| 226 return string.trim().length > 0; | |
| 227 } | |
| 228 | |
| 229 /** | |
| 230 * @param {string} string | |
| 231 */ | |
| 232 function ignoreStackFrameAndMutableData(string) { | |
| 233 var buffer = string.replace(/\u200b/g, ''); | |
| 234 buffer = buffer.replace(/VM\d+/g, 'VM'); | |
| 235 return buffer.replace(/^\s+at [^\]]+(]?)$/, '$1'); | |
| 236 } | |
| 237 | |
| 238 messageFormatter = messageFormatter || TestRunner.textContentWithLineBreaks; | |
| 239 var buffer = messageFormatter(node); | |
| 240 return buffer.split('\n').map(ignoreStackFrameAndMutableData).filter(isNotEmpt yLine).join('\n'); | |
| 241 }; | |
| 242 | |
| 243 /** | |
| 244 * @param {!Element} element | |
| 245 * @param {!ConsoleModel.ConsoleMessage} message | |
| 246 * @return {string} | |
| 247 */ | |
| 248 ConsoleTestRunner.simpleFormatter = function(element, message) { | |
| 249 return message.messageText + ':' + message.line + ':' + message.column; | |
| 250 }; | |
| 251 | |
| 252 /** | |
| 253 * @param {boolean=} printOriginatingCommand | |
| 254 * @param {boolean=} dumpClassNames | |
| 255 * @param {!ConsoleTestRunner.Formatter=} messageFormatter | |
| 256 */ | |
| 257 ConsoleTestRunner.dumpConsoleMessagesIgnoreErrorStackFrames = function( | |
| 258 printOriginatingCommand, dumpClassNames, messageFormatter) { | |
| 259 TestRunner.addResults(ConsoleTestRunner.dumpConsoleMessagesIntoArray( | |
| 260 printOriginatingCommand, dumpClassNames, | |
| 261 messageFormatter ? ConsoleTestRunner.formatterIgnoreStackFrameUrls.bind(th is, messageFormatter) : undefined)); | |
| 262 }; | |
| 263 | |
| 264 ConsoleTestRunner.dumpConsoleMessagesWithStyles = function() { | |
| 265 var messageViews = Console.ConsoleView.instance()._visibleViewMessages; | |
| 266 for (var i = 0; i < messageViews.length; ++i) { | |
| 267 var element = messageViews[i].element(); | |
| 268 var messageText = ConsoleTestRunner.prepareConsoleMessageText(element); | |
| 269 TestRunner.addResult(messageText); | |
| 270 var spans = element.querySelectorAll('.console-message-text *'); | |
| 271 for (var j = 0; j < spans.length; ++j) | |
| 272 TestRunner.addResult('Styled text #' + j + ': ' + (spans[j].style.cssText || 'NO STYLES DEFINED')); | |
| 273 } | |
| 274 }; | |
| 275 | |
| 276 /** | |
| 277 * @param {boolean=} sortMessages | |
| 278 */ | |
| 279 ConsoleTestRunner.dumpConsoleMessagesWithClasses = function(sortMessages) { | |
| 280 var result = []; | |
| 281 var messageViews = Console.ConsoleView.instance()._visibleViewMessages; | |
| 282 for (var i = 0; i < messageViews.length; ++i) { | |
| 283 var element = messageViews[i].element(); | |
| 284 var contentElement = messageViews[i].contentElement(); | |
| 285 var messageText = ConsoleTestRunner.prepareConsoleMessageText(element); | |
| 286 result.push(messageText + ' ' + element.getAttribute('class') + ' > ' + cont entElement.getAttribute('class')); | |
| 287 } | |
| 288 if (sortMessages) | |
| 289 result.sort(); | |
| 290 for (var i = 0; i < result.length; ++i) | |
| 291 TestRunner.addResult(result[i]); | |
|
caseq
2017/07/12 01:04:33
just TestRunner.addResults(result)?
chenwilliam
2017/07/12 22:02:58
Done.
| |
| 292 }; | |
| 293 | |
| 294 ConsoleTestRunner.dumpConsoleClassesBrief = function() { | |
| 295 var messageViews = Console.ConsoleView.instance()._visibleViewMessages; | |
| 296 for (var i = 0; i < messageViews.length; ++i) | |
| 297 TestRunner.addResult(messageViews[i].toMessageElement().className); | |
| 298 }; | |
| 299 | |
| 300 ConsoleTestRunner.dumpConsoleCounters = function() { | |
| 301 var counter = Counters.WarningErrorCounter._instanceForTest; | |
| 302 for (var index = 0; index < counter._titles.length; ++index) | |
| 303 TestRunner.addResult(counter._titles[index]); | |
| 304 ConsoleTestRunner.dumpConsoleClassesBrief(); | |
| 305 }; | |
| 306 | |
| 307 /** | |
| 308 * @param {!Function} callback | |
| 309 * @param {function(!Element):boolean} deepFilter | |
| 310 * @param {function(!ObjectUI.ObjectPropertiesSection):boolean} sectionFilter | |
| 311 */ | |
| 312 ConsoleTestRunner.expandConsoleMessages = function(callback, deepFilter, section Filter) { | |
|
caseq
2017/07/12 01:04:33
Let's switch all these to promises!
chenwilliam
2017/07/12 22:02:58
See earlier comment about promisifying the API.
| |
| 313 Console.ConsoleView.instance()._invalidateViewport(); | |
| 314 var messageViews = Console.ConsoleView.instance()._visibleViewMessages; | |
| 315 | |
| 316 // Initiate round-trips to fetch necessary data for further rendering. | |
| 317 for (var i = 0; i < messageViews.length; ++i) | |
| 318 messageViews[i].element(); | |
| 319 | |
| 320 TestRunner.deprecatedRunAfterPendingDispatches(expandTreeElements); | |
| 321 | |
| 322 function expandTreeElements() { | |
| 323 for (var i = 0; i < messageViews.length; ++i) { | |
| 324 var element = messageViews[i].element(); | |
| 325 for (var node = element; node; node = node.traverseNextNode(element)) { | |
| 326 if (node.treeElementForTest) | |
| 327 node.treeElementForTest.expand(); | |
| 328 if (node._expandStackTraceForTest) | |
| 329 node._expandStackTraceForTest(); | |
| 330 if (!node._section) | |
| 331 continue; | |
| 332 if (sectionFilter && !sectionFilter(node._section)) | |
| 333 continue; | |
| 334 node._section.expand(); | |
| 335 | |
| 336 if (!deepFilter) | |
| 337 continue; | |
| 338 var treeElements = node._section.rootElement().children(); | |
| 339 for (var j = 0; j < treeElements.length; ++j) { | |
| 340 for (var treeElement = treeElements[j]; treeElement; | |
| 341 treeElement = treeElement.traverseNextTreeElement(true, null, tru e)) { | |
| 342 if (deepFilter(treeElement)) | |
| 343 treeElement.expand(); | |
| 344 } | |
| 345 } | |
| 346 } | |
| 347 } | |
| 348 TestRunner.deprecatedRunAfterPendingDispatches(callback); | |
| 349 } | |
| 350 }; | |
| 351 | |
| 352 /** | |
| 353 * @param {!Function} callback | |
| 354 */ | |
| 355 ConsoleTestRunner.expandGettersInConsoleMessages = function(callback) { | |
| 356 var messageViews = Console.ConsoleView.instance()._visibleViewMessages; | |
| 357 var properties = []; | |
| 358 var propertiesCount = 0; | |
| 359 TestRunner.addSniffer(ObjectUI.ObjectPropertyTreeElement.prototype, '_updateEx pandable', propertyExpandableUpdated); | |
| 360 for (var i = 0; i < messageViews.length; ++i) { | |
| 361 var element = messageViews[i].element(); | |
| 362 for (var node = element; node; node = node.traverseNextNode(element)) { | |
| 363 if (node.classList && node.classList.contains('object-value-calculate-valu e-button')) { | |
| 364 ++propertiesCount; | |
| 365 node.click(); | |
| 366 properties.push(node.parentElement.parentElement); | |
| 367 } | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 function propertyExpandableUpdated() { | |
| 372 --propertiesCount; | |
| 373 if (propertiesCount === 0) { | |
| 374 for (var i = 0; i < properties.length; ++i) | |
| 375 properties[i].click(); | |
| 376 TestRunner.deprecatedRunAfterPendingDispatches(callback); | |
| 377 } else { | |
| 378 TestRunner.addSniffer( | |
| 379 ObjectUI.ObjectPropertyTreeElement.prototype, '_updateExpandable', pro pertyExpandableUpdated); | |
| 380 } | |
| 381 } | |
| 382 }; | |
| 383 | |
| 384 /** | |
| 385 * @param {!Function} callback | |
| 386 */ | |
| 387 ConsoleTestRunner.expandConsoleMessagesErrorParameters = function(callback) { | |
| 388 var messageViews = Console.ConsoleView.instance()._visibleViewMessages; | |
| 389 // Initiate round-trips to fetch necessary data for further rendering. | |
| 390 for (var i = 0; i < messageViews.length; ++i) | |
| 391 messageViews[i].element(); | |
| 392 TestRunner.deprecatedRunAfterPendingDispatches(callback); | |
| 393 }; | |
| 394 | |
| 395 /** | |
| 396 * @param {!Function} callback | |
| 397 */ | |
| 398 ConsoleTestRunner.waitForRemoteObjectsConsoleMessages = function(callback) { | |
| 399 var messages = Console.ConsoleView.instance()._visibleViewMessages; | |
| 400 for (var i = 0; i < messages.length; ++i) | |
| 401 messages[i].toMessageElement(); | |
| 402 TestRunner.deprecatedRunAfterPendingDispatches(callback); | |
| 403 }; | |
| 404 | |
| 405 /** | |
| 406 * @return {!Promise} | |
| 407 */ | |
| 408 ConsoleTestRunner.waitUntilConsoleEditorLoaded = function() { | |
| 409 var fulfill; | |
| 410 var promise = new Promise(x => (fulfill = x)); | |
| 411 var editor = Console.ConsoleView.instance()._prompt._editor; | |
| 412 if (editor) | |
| 413 fulfill(editor); | |
| 414 else | |
| 415 TestRunner.addSniffer(Console.ConsolePrompt.prototype, '_editorSetForTest', _ => fulfill(editor)); | |
| 416 return promise; | |
| 417 }; | |
| 418 | |
| 419 /** | |
| 420 * @param {!Function} callback | |
| 421 */ | |
| 422 ConsoleTestRunner.waitUntilMessageReceived = function(callback) { | |
| 423 TestRunner.addSniffer(ConsoleModel.consoleModel, 'addMessage', callback, false ); | |
| 424 }; | |
| 425 | |
| 426 /** | |
| 427 * @return {!Promise} | |
| 428 */ | |
| 429 ConsoleTestRunner.waitUntilMessageReceivedPromise = function() { | |
| 430 var callback; | |
| 431 var promise = new Promise(fullfill => (callback = fullfill)); | |
| 432 ConsoleTestRunner.waitUntilMessageReceived(callback); | |
| 433 return promise; | |
| 434 }; | |
| 435 | |
| 436 /** | |
| 437 * @param {number} count | |
| 438 * @param {!Function} callback | |
| 439 */ | |
| 440 ConsoleTestRunner.waitUntilNthMessageReceived = function(count, callback) { | |
| 441 function override() { | |
| 442 if (--count === 0) | |
| 443 TestRunner.safeWrap(callback)(); | |
| 444 else | |
| 445 TestRunner.addSniffer(ConsoleModel.consoleModel, 'addMessage', override, f alse); | |
| 446 } | |
| 447 TestRunner.addSniffer(ConsoleModel.consoleModel, 'addMessage', override, false ); | |
| 448 }; | |
| 449 | |
| 450 /** | |
| 451 * @param {number} count | |
| 452 * @return {!Promise} | |
| 453 */ | |
| 454 ConsoleTestRunner.waitUntilNthMessageReceivedPromise = function(count) { | |
| 455 var callback; | |
| 456 var promise = new Promise(fullfill => (callback = fullfill)); | |
| 457 ConsoleTestRunner.waitUntilNthMessageReceived(count, callback); | |
| 458 return promise; | |
| 459 }; | |
| 460 | |
| 461 /** | |
| 462 * @param {string} namePrefix | |
| 463 */ | |
| 464 ConsoleTestRunner.changeExecutionContext = function(namePrefix) { | |
| 465 var selector = Console.ConsoleView.instance()._consoleContextSelector; | |
| 466 for (var executionContext of selector._items) { | |
| 467 if (selector.titleFor(executionContext).startsWith(namePrefix)) { | |
| 468 UI.context.setFlavor(SDK.ExecutionContext, executionContext); | |
| 469 return; | |
| 470 } | |
| 471 } | |
| 472 TestRunner.addResult('FAILED: context with prefix: ' + namePrefix + ' not foun d in the context list'); | |
| 473 }; | |
| 474 | |
| 475 /** | |
| 476 * @param {number} expectedCount | |
| 477 * @param {!Function} callback | |
| 478 */ | |
| 479 ConsoleTestRunner.waitForConsoleMessages = function(expectedCount, callback) { | |
| 480 var consoleView = Console.ConsoleView.instance(); | |
| 481 checkAndReturn(); | |
| 482 | |
| 483 function checkAndReturn() { | |
| 484 if (consoleView._visibleViewMessages.length === expectedCount) { | |
| 485 TestRunner.addResult('Message count: ' + expectedCount); | |
| 486 callback(); | |
| 487 } else { | |
| 488 TestRunner.addSniffer(consoleView, '_messageAppendedForTests', checkAndRet urn); | |
| 489 } | |
| 490 } | |
| 491 }; | |
| 492 | |
| 493 /** | |
| 494 * @param {number} fromMessage | |
| 495 * @param {number} fromTextOffset | |
| 496 * @param {number} toMessage | |
| 497 * @param {number} toTextOffset | |
| 498 * @suppressGlobalPropertiesCheck | |
| 499 */ | |
| 500 ConsoleTestRunner.selectConsoleMessages = function(fromMessage, fromTextOffset, toMessage, toTextOffset) { | |
| 501 var consoleView = Console.ConsoleView.instance(); | |
| 502 var from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).el ement(), fromTextOffset); | |
| 503 var to = selectionContainerAndOffset(consoleView.itemElement(toMessage).elemen t(), toTextOffset); | |
| 504 window.getSelection().setBaseAndExtent(from.container, from.offset, to.contain er, to.offset); | |
| 505 | |
| 506 /** | |
| 507 * @param {!Node} container | |
| 508 * @param {number} offset | |
| 509 * @return {?{container: !Node, offset: number}} | |
| 510 */ | |
| 511 function selectionContainerAndOffset(container, offset) { | |
| 512 /** @type {?Node} */ | |
| 513 var node = container; | |
| 514 if (offset === 0 && container.nodeType !== Node.TEXT_NODE) { | |
| 515 container = /** @type {!Node} */ (container.traverseNextTextNode()); | |
| 516 node = container; | |
| 517 } | |
| 518 var charCount = 0; | |
| 519 while ((node = node.traverseNextTextNode(container))) { | |
| 520 var length = node.textContent.length; | |
| 521 if (charCount + length >= offset) | |
| 522 return {container: node, offset: offset - charCount}; | |
| 523 | |
| 524 charCount += length; | |
| 525 } | |
| 526 return null; | |
| 527 } | |
| 528 }; | |
| OLD | NEW |