| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. | |
| 3 * Copyright (C) 2009 Joseph Pecoraro | |
| 4 * | |
| 5 * Redistribution and use in source and binary forms, with or without | |
| 6 * modification, are permitted provided that the following conditions | |
| 7 * are met: | |
| 8 * 1. Redistributions of source code must retain the above copyright | |
| 9 * notice, this list of conditions and the following disclaimer. | |
| 10 * 2. Redistributions in binary form must reproduce the above copyright | |
| 11 * notice, this list of conditions and the following disclaimer in the | |
| 12 * documentation and/or other materials provided with the distribution. | |
| 13 * | |
| 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | |
| 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | |
| 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 25 */ | |
| 26 | |
| 27 /** | |
| 28 * @unrestricted | |
| 29 */ | |
| 30 Components.ObjectPropertiesSection = class extends UI.TreeOutlineInShadow { | |
| 31 /** | |
| 32 * @param {!SDK.RemoteObject} object | |
| 33 * @param {?string|!Element=} title | |
| 34 * @param {!Components.Linkifier=} linkifier | |
| 35 * @param {?string=} emptyPlaceholder | |
| 36 * @param {boolean=} ignoreHasOwnProperty | |
| 37 * @param {!Array.<!SDK.RemoteObjectProperty>=} extraProperties | |
| 38 */ | |
| 39 constructor(object, title, linkifier, emptyPlaceholder, ignoreHasOwnProperty,
extraProperties) { | |
| 40 super(); | |
| 41 this._object = object; | |
| 42 this._editable = true; | |
| 43 this.hideOverflow(); | |
| 44 this.setFocusable(false); | |
| 45 this._objectTreeElement = new Components.ObjectPropertiesSection.RootElement
( | |
| 46 object, linkifier, emptyPlaceholder, ignoreHasOwnProperty, extraProperti
es); | |
| 47 this.appendChild(this._objectTreeElement); | |
| 48 if (typeof title === 'string' || !title) { | |
| 49 this.titleElement = this.element.createChild('span'); | |
| 50 this.titleElement.textContent = title || ''; | |
| 51 } else { | |
| 52 this.titleElement = title; | |
| 53 this.element.appendChild(title); | |
| 54 } | |
| 55 | |
| 56 this.element._section = this; | |
| 57 this.registerRequiredCSS('components/objectValue.css'); | |
| 58 this.registerRequiredCSS('components/objectPropertiesSection.css'); | |
| 59 this.rootElement().childrenListElement.classList.add('source-code', 'object-
properties-section'); | |
| 60 } | |
| 61 | |
| 62 /** | |
| 63 * @param {!SDK.RemoteObject} object | |
| 64 * @param {!Components.Linkifier=} linkifier | |
| 65 * @param {boolean=} skipProto | |
| 66 * @return {!Element} | |
| 67 */ | |
| 68 static defaultObjectPresentation(object, linkifier, skipProto) { | |
| 69 var componentRoot = createElementWithClass('span', 'source-code'); | |
| 70 var shadowRoot = UI.createShadowRootWithCoreStyles(componentRoot, 'component
s/objectValue.css'); | |
| 71 shadowRoot.appendChild( | |
| 72 Components.ObjectPropertiesSection.createValueElement(object, false /* w
asThrown */, true /* showPreview */)); | |
| 73 if (!object.hasChildren) | |
| 74 return componentRoot; | |
| 75 | |
| 76 var objectPropertiesSection = new Components.ObjectPropertiesSection(object,
componentRoot, linkifier); | |
| 77 objectPropertiesSection.editable = false; | |
| 78 if (skipProto) | |
| 79 objectPropertiesSection.skipProto(); | |
| 80 | |
| 81 return objectPropertiesSection.element; | |
| 82 } | |
| 83 | |
| 84 /** | |
| 85 * @param {!SDK.RemoteObjectProperty} propertyA | |
| 86 * @param {!SDK.RemoteObjectProperty} propertyB | |
| 87 * @return {number} | |
| 88 */ | |
| 89 static CompareProperties(propertyA, propertyB) { | |
| 90 var a = propertyA.name; | |
| 91 var b = propertyB.name; | |
| 92 if (a === '__proto__') | |
| 93 return 1; | |
| 94 if (b === '__proto__') | |
| 95 return -1; | |
| 96 if (!propertyA.enumerable && propertyB.enumerable) | |
| 97 return 1; | |
| 98 if (!propertyB.enumerable && propertyA.enumerable) | |
| 99 return -1; | |
| 100 if (a.startsWith('_') && !b.startsWith('_')) | |
| 101 return 1; | |
| 102 if (b.startsWith('_') && !a.startsWith('_')) | |
| 103 return -1; | |
| 104 if (propertyA.symbol && !propertyB.symbol) | |
| 105 return 1; | |
| 106 if (propertyB.symbol && !propertyA.symbol) | |
| 107 return -1; | |
| 108 return String.naturalOrderComparator(a, b); | |
| 109 } | |
| 110 | |
| 111 /** | |
| 112 * @param {?string} name | |
| 113 * @return {!Element} | |
| 114 */ | |
| 115 static createNameElement(name) { | |
| 116 var nameElement = createElementWithClass('span', 'name'); | |
| 117 if (/^\s|\s$|^$|\n/.test(name)) | |
| 118 nameElement.createTextChildren('"', name.replace(/\n/g, '\u21B5'), '"'); | |
| 119 else | |
| 120 nameElement.textContent = name; | |
| 121 return nameElement; | |
| 122 } | |
| 123 | |
| 124 /** | |
| 125 * @param {?string=} description | |
| 126 * @param {boolean=} includePreview | |
| 127 * @param {string=} defaultName | |
| 128 * @return {!Element} valueElement | |
| 129 */ | |
| 130 static valueElementForFunctionDescription(description, includePreview, default
Name) { | |
| 131 var valueElement = createElementWithClass('span', 'object-value-function'); | |
| 132 var text = description ? description.replace(/^function [gs]et /, 'function
') : ''; | |
| 133 defaultName = defaultName || ''; | |
| 134 | |
| 135 // This set of best-effort regular expressions captures common function desc
riptions. | |
| 136 // Ideally, some parser would provide prefix, arguments, function body text
separately. | |
| 137 var isAsync = text.startsWith('async function '); | |
| 138 var isGenerator = text.startsWith('function* '); | |
| 139 var isGeneratorShorthand = text.startsWith('*'); | |
| 140 var isBasic = !isGenerator && text.startsWith('function '); | |
| 141 var isClass = text.startsWith('class ') || text.startsWith('class{'); | |
| 142 var firstArrowIndex = text.indexOf('=>'); | |
| 143 var isArrow = !isAsync && !isGenerator && !isBasic && !isClass && firstArrow
Index > 0; | |
| 144 | |
| 145 var textAfterPrefix; | |
| 146 if (isClass) { | |
| 147 textAfterPrefix = text.substring('class'.length); | |
| 148 var classNameMatch = /^[^{\s]+/.exec(textAfterPrefix.trim()); | |
| 149 var className = defaultName; | |
| 150 if (classNameMatch) | |
| 151 className = classNameMatch[0].trim() || defaultName; | |
| 152 addElements('class', textAfterPrefix, className); | |
| 153 } else if (isAsync) { | |
| 154 textAfterPrefix = text.substring('async function'.length); | |
| 155 addElements('async function', textAfterPrefix, nameAndArguments(textAfterP
refix)); | |
| 156 } else if (isGenerator) { | |
| 157 textAfterPrefix = text.substring('function*'.length); | |
| 158 addElements('function*', textAfterPrefix, nameAndArguments(textAfterPrefix
)); | |
| 159 } else if (isGeneratorShorthand) { | |
| 160 textAfterPrefix = text.substring('*'.length); | |
| 161 addElements('function*', textAfterPrefix, nameAndArguments(textAfterPrefix
)); | |
| 162 } else if (isBasic) { | |
| 163 textAfterPrefix = text.substring('function'.length); | |
| 164 addElements('function', textAfterPrefix, nameAndArguments(textAfterPrefix)
); | |
| 165 } else if (isArrow) { | |
| 166 const maxArrowFunctionCharacterLength = 60; | |
| 167 var abbreviation = text; | |
| 168 if (defaultName) | |
| 169 abbreviation = defaultName + '()'; | |
| 170 else if (text.length > maxArrowFunctionCharacterLength) | |
| 171 abbreviation = text.substring(0, firstArrowIndex + 2) + ' {\u2026}'; | |
| 172 addElements('', text, abbreviation); | |
| 173 } else { | |
| 174 addElements('function', text, nameAndArguments(text)); | |
| 175 } | |
| 176 valueElement.title = description || ''; | |
| 177 return valueElement; | |
| 178 | |
| 179 /** | |
| 180 * @param {string} contents | |
| 181 * @return {string} | |
| 182 */ | |
| 183 function nameAndArguments(contents) { | |
| 184 var startOfArgumentsIndex = contents.indexOf('('); | |
| 185 var endOfArgumentsMatch = contents.match(/\)\s*{/); | |
| 186 if (startOfArgumentsIndex !== -1 && endOfArgumentsMatch && endOfArgumentsM
atch.index > startOfArgumentsIndex) { | |
| 187 var name = contents.substring(0, startOfArgumentsIndex).trim() || defaul
tName; | |
| 188 var args = contents.substring(startOfArgumentsIndex, endOfArgumentsMatch
.index + 1); | |
| 189 return name + args; | |
| 190 } | |
| 191 return defaultName + '()'; | |
| 192 } | |
| 193 | |
| 194 /** | |
| 195 * @param {string} prefix | |
| 196 * @param {string} body | |
| 197 * @param {string} abbreviation | |
| 198 */ | |
| 199 function addElements(prefix, body, abbreviation) { | |
| 200 const maxFunctionBodyLength = 200; | |
| 201 if (prefix.length) | |
| 202 valueElement.createChild('span', 'object-value-function-prefix').textCon
tent = prefix + ' '; | |
| 203 if (includePreview) | |
| 204 valueElement.createTextChild(body.trim().trimEnd(maxFunctionBodyLength))
; | |
| 205 else | |
| 206 valueElement.createTextChild(abbreviation.replace(/\n/g, ' ')); | |
| 207 } | |
| 208 } | |
| 209 | |
| 210 /** | |
| 211 * @param {!SDK.RemoteObject} value | |
| 212 * @param {boolean} wasThrown | |
| 213 * @param {boolean} showPreview | |
| 214 * @param {!Element=} parentElement | |
| 215 * @param {!Components.Linkifier=} linkifier | |
| 216 * @return {!Element} | |
| 217 */ | |
| 218 static createValueElementWithCustomSupport(value, wasThrown, showPreview, pare
ntElement, linkifier) { | |
| 219 if (value.customPreview()) { | |
| 220 var result = (new Components.CustomPreviewComponent(value)).element; | |
| 221 result.classList.add('object-properties-section-custom-section'); | |
| 222 return result; | |
| 223 } | |
| 224 return Components.ObjectPropertiesSection.createValueElement( | |
| 225 value, wasThrown, showPreview, parentElement, linkifier); | |
| 226 } | |
| 227 | |
| 228 /** | |
| 229 * @param {!SDK.RemoteObject} value | |
| 230 * @param {boolean} wasThrown | |
| 231 * @param {boolean} showPreview | |
| 232 * @param {!Element=} parentElement | |
| 233 * @param {!Components.Linkifier=} linkifier | |
| 234 * @return {!Element} | |
| 235 */ | |
| 236 static createValueElement(value, wasThrown, showPreview, parentElement, linkif
ier) { | |
| 237 var valueElement; | |
| 238 var type = value.type; | |
| 239 var subtype = value.subtype; | |
| 240 var description = value.description; | |
| 241 if (type === 'object' && subtype === 'internal#location') { | |
| 242 var rawLocation = value.debuggerModel().createRawLocationByScriptId( | |
| 243 value.value.scriptId, value.value.lineNumber, value.value.columnNumber
); | |
| 244 if (rawLocation && linkifier) | |
| 245 return linkifier.linkifyRawLocation(rawLocation, ''); | |
| 246 valueElement = createUnknownInternalLocationElement(); | |
| 247 } else if (type === 'string' && typeof description === 'string') { | |
| 248 valueElement = createStringElement(); | |
| 249 } else if (type === 'function') { | |
| 250 valueElement = Components.ObjectPropertiesSection.valueElementForFunctionD
escription(description); | |
| 251 } else if (type === 'object' && subtype === 'node' && description) { | |
| 252 valueElement = createNodeElement(); | |
| 253 } else if (type === 'number' && description && description.indexOf('e') !==
-1) { | |
| 254 valueElement = createNumberWithExponentElement(); | |
| 255 if (parentElement) // FIXME: do it in the caller. | |
| 256 parentElement.classList.add('hbox'); | |
| 257 } else { | |
| 258 valueElement = createElementWithClass('span', 'object-value-' + (subtype |
| type)); | |
| 259 valueElement.title = description || ''; | |
| 260 if (Runtime.experiments.isEnabled('objectPreviews') && value.preview && sh
owPreview) { | |
| 261 var previewFormatter = new Components.RemoteObjectPreviewFormatter(); | |
| 262 previewFormatter.appendObjectPreview(valueElement, value.preview, false
/* isEntry */); | |
| 263 } else { | |
| 264 valueElement.setTextContentTruncatedIfNeeded(description); | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 if (wasThrown) { | |
| 269 var wrapperElement = createElementWithClass('span', 'error value'); | |
| 270 wrapperElement.createTextChild('[' + Common.UIString('Exception') + ': '); | |
| 271 wrapperElement.appendChild(valueElement); | |
| 272 wrapperElement.createTextChild(']'); | |
| 273 return wrapperElement; | |
| 274 } | |
| 275 valueElement.classList.add('value'); | |
| 276 return valueElement; | |
| 277 | |
| 278 /** | |
| 279 * @return {!Element} | |
| 280 */ | |
| 281 function createUnknownInternalLocationElement() { | |
| 282 var valueElement = createElementWithClass('span'); | |
| 283 valueElement.textContent = '<' + Common.UIString('unknown') + '>'; | |
| 284 valueElement.title = description || ''; | |
| 285 return valueElement; | |
| 286 } | |
| 287 | |
| 288 /** | |
| 289 * @return {!Element} | |
| 290 */ | |
| 291 function createStringElement() { | |
| 292 var valueElement = createElementWithClass('span', 'object-value-string'); | |
| 293 valueElement.createChild('span', 'object-value-string-quote').textContent
= '"'; | |
| 294 valueElement.createTextChild('').setTextContentTruncatedIfNeeded(descripti
on.replace(/\n/g, '\u21B5')); | |
| 295 valueElement.createChild('span', 'object-value-string-quote').textContent
= '"'; | |
| 296 valueElement.title = description || ''; | |
| 297 return valueElement; | |
| 298 } | |
| 299 | |
| 300 /** | |
| 301 * @return {!Element} | |
| 302 */ | |
| 303 function createNodeElement() { | |
| 304 var valueElement = createElementWithClass('span', 'object-value-node'); | |
| 305 Components.DOMPresentationUtils.createSpansForNodeTitle(valueElement, /**
@type {string} */ (description)); | |
| 306 valueElement.addEventListener('click', event => { | |
| 307 Common.Revealer.reveal(value); | |
| 308 event.consume(true); | |
| 309 }, false); | |
| 310 valueElement.addEventListener('mousemove', () => SDK.DOMModel.highlightObj
ectAsDOMNode(value), false); | |
| 311 valueElement.addEventListener('mouseleave', () => SDK.DOMModel.hideDOMNode
Highlight(), false); | |
| 312 return valueElement; | |
| 313 } | |
| 314 | |
| 315 /** | |
| 316 * @return {!Element} | |
| 317 */ | |
| 318 function createNumberWithExponentElement() { | |
| 319 var valueElement = createElementWithClass('span', 'object-value-number'); | |
| 320 var numberParts = description.split('e'); | |
| 321 valueElement.createChild('span', 'object-value-scientific-notation-mantiss
a').textContent = numberParts[0]; | |
| 322 valueElement.createChild('span', 'object-value-scientific-notation-exponen
t').textContent = 'e' + numberParts[1]; | |
| 323 valueElement.classList.add('object-value-scientific-notation-number'); | |
| 324 valueElement.title = description || ''; | |
| 325 return valueElement; | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 /** | |
| 330 * @param {!SDK.RemoteObject} func | |
| 331 * @param {!Element} element | |
| 332 * @param {boolean} linkify | |
| 333 * @param {boolean=} includePreview | |
| 334 */ | |
| 335 static formatObjectAsFunction(func, element, linkify, includePreview) { | |
| 336 func.debuggerModel().functionDetailsPromise(func).then(didGetDetails); | |
| 337 | |
| 338 /** | |
| 339 * @param {?SDK.DebuggerModel.FunctionDetails} response | |
| 340 */ | |
| 341 function didGetDetails(response) { | |
| 342 if (linkify && response && response.location) { | |
| 343 element.classList.add('linkified'); | |
| 344 element.addEventListener('click', () => Common.Revealer.reveal(response.
location)); | |
| 345 } | |
| 346 | |
| 347 // The includePreview flag is false for formats such as console.dir(). | |
| 348 var defaultName = includePreview ? '' : 'anonymous'; | |
| 349 if (response && response.functionName) | |
| 350 defaultName = response.functionName; | |
| 351 var valueElement = Components.ObjectPropertiesSection.valueElementForFunct
ionDescription( | |
| 352 func.description, includePreview, defaultName); | |
| 353 element.appendChild(valueElement); | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 skipProto() { | |
| 358 this._skipProto = true; | |
| 359 } | |
| 360 | |
| 361 expand() { | |
| 362 this._objectTreeElement.expand(); | |
| 363 } | |
| 364 | |
| 365 /** | |
| 366 * @param {boolean} value | |
| 367 */ | |
| 368 setEditable(value) { | |
| 369 this._editable = value; | |
| 370 } | |
| 371 | |
| 372 /** | |
| 373 * @return {!UI.TreeElement} | |
| 374 */ | |
| 375 objectTreeElement() { | |
| 376 return this._objectTreeElement; | |
| 377 } | |
| 378 | |
| 379 enableContextMenu() { | |
| 380 this.element.addEventListener('contextmenu', this._contextMenuEventFired.bin
d(this), false); | |
| 381 } | |
| 382 | |
| 383 _contextMenuEventFired(event) { | |
| 384 var contextMenu = new UI.ContextMenu(event); | |
| 385 contextMenu.appendApplicableItems(this._object); | |
| 386 contextMenu.show(); | |
| 387 } | |
| 388 | |
| 389 titleLessMode() { | |
| 390 this._objectTreeElement.listItemElement.classList.add('hidden'); | |
| 391 this._objectTreeElement.childrenListElement.classList.add('title-less-mode')
; | |
| 392 this._objectTreeElement.expand(); | |
| 393 } | |
| 394 }; | |
| 395 | |
| 396 /** @const */ | |
| 397 Components.ObjectPropertiesSection._arrayLoadThreshold = 100; | |
| 398 | |
| 399 | |
| 400 /** | |
| 401 * @unrestricted | |
| 402 */ | |
| 403 Components.ObjectPropertiesSection.RootElement = class extends UI.TreeElement { | |
| 404 /** | |
| 405 * @param {!SDK.RemoteObject} object | |
| 406 * @param {!Components.Linkifier=} linkifier | |
| 407 * @param {?string=} emptyPlaceholder | |
| 408 * @param {boolean=} ignoreHasOwnProperty | |
| 409 * @param {!Array.<!SDK.RemoteObjectProperty>=} extraProperties | |
| 410 */ | |
| 411 constructor(object, linkifier, emptyPlaceholder, ignoreHasOwnProperty, extraPr
operties) { | |
| 412 var contentElement = createElement('content'); | |
| 413 super(contentElement); | |
| 414 | |
| 415 this._object = object; | |
| 416 this._extraProperties = extraProperties || []; | |
| 417 this._ignoreHasOwnProperty = !!ignoreHasOwnProperty; | |
| 418 this._emptyPlaceholder = emptyPlaceholder; | |
| 419 | |
| 420 this.setExpandable(true); | |
| 421 this.selectable = false; | |
| 422 this.toggleOnClick = true; | |
| 423 this.listItemElement.classList.add('object-properties-section-root-element')
; | |
| 424 this._linkifier = linkifier; | |
| 425 } | |
| 426 | |
| 427 /** | |
| 428 * @override | |
| 429 */ | |
| 430 onexpand() { | |
| 431 if (this.treeOutline) | |
| 432 this.treeOutline.element.classList.add('expanded'); | |
| 433 } | |
| 434 | |
| 435 /** | |
| 436 * @override | |
| 437 */ | |
| 438 oncollapse() { | |
| 439 if (this.treeOutline) | |
| 440 this.treeOutline.element.classList.remove('expanded'); | |
| 441 } | |
| 442 | |
| 443 /** | |
| 444 * @override | |
| 445 * @param {!Event} e | |
| 446 * @return {boolean} | |
| 447 */ | |
| 448 ondblclick(e) { | |
| 449 return true; | |
| 450 } | |
| 451 | |
| 452 /** | |
| 453 * @override | |
| 454 */ | |
| 455 onpopulate() { | |
| 456 Components.ObjectPropertyTreeElement._populate( | |
| 457 this, this._object, !!this.treeOutline._skipProto, this._linkifier, this
._emptyPlaceholder, | |
| 458 this._ignoreHasOwnProperty, this._extraProperties); | |
| 459 } | |
| 460 }; | |
| 461 | |
| 462 /** | |
| 463 * @unrestricted | |
| 464 */ | |
| 465 Components.ObjectPropertyTreeElement = class extends UI.TreeElement { | |
| 466 /** | |
| 467 * @param {!SDK.RemoteObjectProperty} property | |
| 468 * @param {!Components.Linkifier=} linkifier | |
| 469 */ | |
| 470 constructor(property, linkifier) { | |
| 471 // Pass an empty title, the title gets made later in onattach. | |
| 472 super(); | |
| 473 | |
| 474 this.property = property; | |
| 475 this.toggleOnClick = true; | |
| 476 this.selectable = false; | |
| 477 /** @type {!Array.<!Object>} */ | |
| 478 this._highlightChanges = []; | |
| 479 this._linkifier = linkifier; | |
| 480 } | |
| 481 | |
| 482 /** | |
| 483 * @param {!UI.TreeElement} treeElement | |
| 484 * @param {!SDK.RemoteObject} value | |
| 485 * @param {boolean} skipProto | |
| 486 * @param {!Components.Linkifier=} linkifier | |
| 487 * @param {?string=} emptyPlaceholder | |
| 488 * @param {boolean=} flattenProtoChain | |
| 489 * @param {!Array.<!SDK.RemoteObjectProperty>=} extraProperties | |
| 490 * @param {!SDK.RemoteObject=} targetValue | |
| 491 */ | |
| 492 static _populate( | |
| 493 treeElement, | |
| 494 value, | |
| 495 skipProto, | |
| 496 linkifier, | |
| 497 emptyPlaceholder, | |
| 498 flattenProtoChain, | |
| 499 extraProperties, | |
| 500 targetValue) { | |
| 501 if (value.arrayLength() > Components.ObjectPropertiesSection._arrayLoadThres
hold) { | |
| 502 treeElement.removeChildren(); | |
| 503 Components.ArrayGroupingTreeElement._populateArray(treeElement, value, 0,
value.arrayLength() - 1, linkifier); | |
| 504 return; | |
| 505 } | |
| 506 | |
| 507 /** | |
| 508 * @param {?Array.<!SDK.RemoteObjectProperty>} properties | |
| 509 * @param {?Array.<!SDK.RemoteObjectProperty>} internalProperties | |
| 510 */ | |
| 511 function callback(properties, internalProperties) { | |
| 512 treeElement.removeChildren(); | |
| 513 if (!properties) | |
| 514 return; | |
| 515 | |
| 516 extraProperties = extraProperties || []; | |
| 517 for (var i = 0; i < extraProperties.length; ++i) | |
| 518 properties.push(extraProperties[i]); | |
| 519 | |
| 520 Components.ObjectPropertyTreeElement.populateWithProperties( | |
| 521 treeElement, properties, internalProperties, skipProto, targetValue ||
value, linkifier, emptyPlaceholder); | |
| 522 } | |
| 523 | |
| 524 var generatePreview = Runtime.experiments.isEnabled('objectPreviews'); | |
| 525 if (flattenProtoChain) | |
| 526 value.getAllProperties(false, generatePreview, callback); | |
| 527 else | |
| 528 SDK.RemoteObject.loadFromObjectPerProto(value, generatePreview, callback); | |
| 529 } | |
| 530 | |
| 531 /** | |
| 532 * @param {!UI.TreeElement} treeNode | |
| 533 * @param {!Array.<!SDK.RemoteObjectProperty>} properties | |
| 534 * @param {?Array.<!SDK.RemoteObjectProperty>} internalProperties | |
| 535 * @param {boolean} skipProto | |
| 536 * @param {?SDK.RemoteObject} value | |
| 537 * @param {!Components.Linkifier=} linkifier | |
| 538 * @param {?string=} emptyPlaceholder | |
| 539 */ | |
| 540 static populateWithProperties( | |
| 541 treeNode, | |
| 542 properties, | |
| 543 internalProperties, | |
| 544 skipProto, | |
| 545 value, | |
| 546 linkifier, | |
| 547 emptyPlaceholder) { | |
| 548 properties.sort(Components.ObjectPropertiesSection.CompareProperties); | |
| 549 | |
| 550 var tailProperties = []; | |
| 551 var protoProperty = null; | |
| 552 for (var i = 0; i < properties.length; ++i) { | |
| 553 var property = properties[i]; | |
| 554 property.parentObject = value; | |
| 555 if (property.name === '__proto__' && !property.isAccessorProperty()) { | |
| 556 protoProperty = property; | |
| 557 continue; | |
| 558 } | |
| 559 | |
| 560 if (property.isOwn && property.getter) { | |
| 561 var getterProperty = new SDK.RemoteObjectProperty('get ' + property.name
, property.getter, false); | |
| 562 getterProperty.parentObject = value; | |
| 563 tailProperties.push(getterProperty); | |
| 564 } | |
| 565 if (property.isOwn && property.setter) { | |
| 566 var setterProperty = new SDK.RemoteObjectProperty('set ' + property.name
, property.setter, false); | |
| 567 setterProperty.parentObject = value; | |
| 568 tailProperties.push(setterProperty); | |
| 569 } | |
| 570 var canShowProperty = property.getter || !property.isAccessorProperty(); | |
| 571 if (canShowProperty && property.name !== '__proto__') | |
| 572 treeNode.appendChild(new Components.ObjectPropertyTreeElement(property,
linkifier)); | |
| 573 } | |
| 574 for (var i = 0; i < tailProperties.length; ++i) | |
| 575 treeNode.appendChild(new Components.ObjectPropertyTreeElement(tailProperti
es[i], linkifier)); | |
| 576 if (!skipProto && protoProperty) | |
| 577 treeNode.appendChild(new Components.ObjectPropertyTreeElement(protoPropert
y, linkifier)); | |
| 578 | |
| 579 if (internalProperties) { | |
| 580 for (var i = 0; i < internalProperties.length; i++) { | |
| 581 internalProperties[i].parentObject = value; | |
| 582 var treeElement = new Components.ObjectPropertyTreeElement(internalPrope
rties[i], linkifier); | |
| 583 if (internalProperties[i].name === '[[Entries]]') { | |
| 584 treeElement.setExpandable(true); | |
| 585 treeElement.expand(); | |
| 586 } | |
| 587 treeNode.appendChild(treeElement); | |
| 588 } | |
| 589 } | |
| 590 | |
| 591 Components.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(treeNod
e, emptyPlaceholder); | |
| 592 } | |
| 593 | |
| 594 /** | |
| 595 * @param {!UI.TreeElement} treeNode | |
| 596 * @param {?string=} emptyPlaceholder | |
| 597 */ | |
| 598 static _appendEmptyPlaceholderIfNeeded(treeNode, emptyPlaceholder) { | |
| 599 if (treeNode.childCount()) | |
| 600 return; | |
| 601 var title = createElementWithClass('div', 'gray-info-message'); | |
| 602 title.textContent = emptyPlaceholder || Common.UIString('No Properties'); | |
| 603 var infoElement = new UI.TreeElement(title); | |
| 604 treeNode.appendChild(infoElement); | |
| 605 } | |
| 606 | |
| 607 /** | |
| 608 * @param {?SDK.RemoteObject} object | |
| 609 * @param {!Array.<string>} propertyPath | |
| 610 * @param {function(?SDK.RemoteObject, boolean=)} callback | |
| 611 * @return {!Element} | |
| 612 */ | |
| 613 static createRemoteObjectAccessorPropertySpan(object, propertyPath, callback)
{ | |
| 614 var rootElement = createElement('span'); | |
| 615 var element = rootElement.createChild('span'); | |
| 616 element.textContent = Common.UIString('(...)'); | |
| 617 if (!object) | |
| 618 return rootElement; | |
| 619 element.classList.add('object-value-calculate-value-button'); | |
| 620 element.title = Common.UIString('Invoke property getter'); | |
| 621 element.addEventListener('click', onInvokeGetterClick, false); | |
| 622 | |
| 623 function onInvokeGetterClick(event) { | |
| 624 event.consume(); | |
| 625 object.getProperty(propertyPath, callback); | |
| 626 } | |
| 627 | |
| 628 return rootElement; | |
| 629 } | |
| 630 | |
| 631 /** | |
| 632 * @param {!RegExp} regex | |
| 633 * @param {string=} additionalCssClassName | |
| 634 * @return {boolean} | |
| 635 */ | |
| 636 setSearchRegex(regex, additionalCssClassName) { | |
| 637 var cssClasses = UI.highlightedSearchResultClassName; | |
| 638 if (additionalCssClassName) | |
| 639 cssClasses += ' ' + additionalCssClassName; | |
| 640 this.revertHighlightChanges(); | |
| 641 | |
| 642 this._applySearch(regex, this.nameElement, cssClasses); | |
| 643 var valueType = this.property.value.type; | |
| 644 if (valueType !== 'object') | |
| 645 this._applySearch(regex, this.valueElement, cssClasses); | |
| 646 | |
| 647 return !!this._highlightChanges.length; | |
| 648 } | |
| 649 | |
| 650 /** | |
| 651 * @param {!RegExp} regex | |
| 652 * @param {!Element} element | |
| 653 * @param {string} cssClassName | |
| 654 */ | |
| 655 _applySearch(regex, element, cssClassName) { | |
| 656 var ranges = []; | |
| 657 var content = element.textContent; | |
| 658 regex.lastIndex = 0; | |
| 659 var match = regex.exec(content); | |
| 660 while (match) { | |
| 661 ranges.push(new Common.SourceRange(match.index, match[0].length)); | |
| 662 match = regex.exec(content); | |
| 663 } | |
| 664 if (ranges.length) | |
| 665 UI.highlightRangesWithStyleClass(element, ranges, cssClassName, this._high
lightChanges); | |
| 666 } | |
| 667 | |
| 668 revertHighlightChanges() { | |
| 669 UI.revertDomChanges(this._highlightChanges); | |
| 670 this._highlightChanges = []; | |
| 671 } | |
| 672 | |
| 673 /** | |
| 674 * @override | |
| 675 */ | |
| 676 onpopulate() { | |
| 677 var propertyValue = /** @type {!SDK.RemoteObject} */ (this.property.value); | |
| 678 console.assert(propertyValue); | |
| 679 var skipProto = this.treeOutline ? this.treeOutline._skipProto : true; | |
| 680 var targetValue = this.property.name !== '__proto__' ? propertyValue : this.
property.parentObject; | |
| 681 Components.ObjectPropertyTreeElement._populate( | |
| 682 this, propertyValue, skipProto, this._linkifier, undefined, undefined, u
ndefined, targetValue); | |
| 683 } | |
| 684 | |
| 685 /** | |
| 686 * @override | |
| 687 * @return {boolean} | |
| 688 */ | |
| 689 ondblclick(event) { | |
| 690 var inEditableElement = event.target.isSelfOrDescendant(this.valueElement) |
| | |
| 691 (this.expandedValueElement && event.target.isSelfOrDescendant(this.expan
dedValueElement)); | |
| 692 if (!this.property.value.customPreview() && inEditableElement && (this.prope
rty.writable || this.property.setter)) | |
| 693 this._startEditing(); | |
| 694 return false; | |
| 695 } | |
| 696 | |
| 697 /** | |
| 698 * @override | |
| 699 */ | |
| 700 onattach() { | |
| 701 this.update(); | |
| 702 this._updateExpandable(); | |
| 703 } | |
| 704 | |
| 705 /** | |
| 706 * @override | |
| 707 */ | |
| 708 onexpand() { | |
| 709 this._showExpandedValueElement(true); | |
| 710 } | |
| 711 | |
| 712 /** | |
| 713 * @override | |
| 714 */ | |
| 715 oncollapse() { | |
| 716 this._showExpandedValueElement(false); | |
| 717 } | |
| 718 | |
| 719 /** | |
| 720 * @param {boolean} value | |
| 721 */ | |
| 722 _showExpandedValueElement(value) { | |
| 723 if (!this.expandedValueElement) | |
| 724 return; | |
| 725 if (value) | |
| 726 this.listItemElement.replaceChild(this.expandedValueElement, this.valueEle
ment); | |
| 727 else | |
| 728 this.listItemElement.replaceChild(this.valueElement, this.expandedValueEle
ment); | |
| 729 } | |
| 730 | |
| 731 /** | |
| 732 * @param {!SDK.RemoteObject} value | |
| 733 * @return {?Element} | |
| 734 */ | |
| 735 _createExpandedValueElement(value) { | |
| 736 var needsAlternateValue = value.hasChildren && !value.customPreview() && val
ue.subtype !== 'node' && | |
| 737 value.type !== 'function' && (value.type !== 'object' || value.preview); | |
| 738 if (!needsAlternateValue) | |
| 739 return null; | |
| 740 | |
| 741 var valueElement = createElementWithClass('span', 'value'); | |
| 742 valueElement.setTextContentTruncatedIfNeeded(value.description || ''); | |
| 743 valueElement.classList.add('object-value-' + (value.subtype || value.type)); | |
| 744 valueElement.title = value.description || ''; | |
| 745 return valueElement; | |
| 746 } | |
| 747 | |
| 748 update() { | |
| 749 this.nameElement = Components.ObjectPropertiesSection.createNameElement(this
.property.name); | |
| 750 if (!this.property.enumerable) | |
| 751 this.nameElement.classList.add('object-properties-section-dimmed'); | |
| 752 if (this.property.synthetic) | |
| 753 this.nameElement.classList.add('synthetic-property'); | |
| 754 | |
| 755 this._updatePropertyPath(); | |
| 756 this.nameElement.addEventListener('contextmenu', this._contextMenuFired.bind
(this, this.property), false); | |
| 757 | |
| 758 var separatorElement = createElementWithClass('span', 'object-properties-sec
tion-separator'); | |
| 759 separatorElement.textContent = ': '; | |
| 760 | |
| 761 if (this.property.value) { | |
| 762 var showPreview = this.property.name !== '__proto__'; | |
| 763 this.valueElement = Components.ObjectPropertiesSection.createValueElementW
ithCustomSupport( | |
| 764 this.property.value, this.property.wasThrown, showPreview, this.listIt
emElement, this._linkifier); | |
| 765 this.valueElement.addEventListener('contextmenu', this._contextMenuFired.b
ind(this, this.property), false); | |
| 766 } else if (this.property.getter) { | |
| 767 this.valueElement = Components.ObjectPropertyTreeElement.createRemoteObjec
tAccessorPropertySpan( | |
| 768 this.property.parentObject, [this.property.name], this._onInvokeGetter
Click.bind(this)); | |
| 769 } else { | |
| 770 this.valueElement = createElementWithClass('span', 'object-value-undefined
'); | |
| 771 this.valueElement.textContent = Common.UIString('<unreadable>'); | |
| 772 this.valueElement.title = Common.UIString('No property getter'); | |
| 773 } | |
| 774 | |
| 775 var valueText = this.valueElement.textContent; | |
| 776 if (this.property.value && valueText && !this.property.wasThrown) | |
| 777 this.expandedValueElement = this._createExpandedValueElement(this.property
.value); | |
| 778 | |
| 779 this.listItemElement.removeChildren(); | |
| 780 this.listItemElement.appendChildren(this.nameElement, separatorElement, this
.valueElement); | |
| 781 } | |
| 782 | |
| 783 _updatePropertyPath() { | |
| 784 if (this.nameElement.title) | |
| 785 return; | |
| 786 | |
| 787 var useDotNotation = /^(_|\$|[A-Z])(_|\$|[A-Z]|\d)*$/i; | |
| 788 var isInteger = /^[1-9]\d*$/; | |
| 789 var name = this.property.name; | |
| 790 var parentPath = this.parent.nameElement ? this.parent.nameElement.title : '
'; | |
| 791 if (useDotNotation.test(name)) | |
| 792 this.nameElement.title = parentPath + '.' + name; | |
| 793 else if (isInteger.test(name)) | |
| 794 this.nameElement.title = parentPath + '[' + name + ']'; | |
| 795 else | |
| 796 this.nameElement.title = parentPath + '["' + name + '"]'; | |
| 797 } | |
| 798 | |
| 799 /** | |
| 800 * @param {!SDK.RemoteObjectProperty} property | |
| 801 * @param {!Event} event | |
| 802 */ | |
| 803 _contextMenuFired(property, event) { | |
| 804 var contextMenu = new UI.ContextMenu(event); | |
| 805 if (property.symbol) | |
| 806 contextMenu.appendApplicableItems(property.symbol); | |
| 807 if (property.value) | |
| 808 contextMenu.appendApplicableItems(property.value); | |
| 809 var copyPathHandler = InspectorFrontendHost.copyText.bind(InspectorFrontendH
ost, this.nameElement.title); | |
| 810 contextMenu.beforeShow(() => { | |
| 811 contextMenu.appendItem(Common.UIString.capitalize('Copy ^property ^path'),
copyPathHandler); | |
| 812 }); | |
| 813 contextMenu.show(); | |
| 814 } | |
| 815 | |
| 816 _startEditing() { | |
| 817 if (this._prompt || !this.treeOutline._editable || this._readOnly) | |
| 818 return; | |
| 819 | |
| 820 this._editableDiv = this.listItemElement.createChild('span'); | |
| 821 | |
| 822 var text = this.property.value.description; | |
| 823 if (this.property.value.type === 'string' && typeof text === 'string') | |
| 824 text = '"' + text + '"'; | |
| 825 | |
| 826 this._editableDiv.setTextContentTruncatedIfNeeded(text, Common.UIString('<st
ring is too large to edit>')); | |
| 827 var originalContent = this._editableDiv.textContent; | |
| 828 | |
| 829 // Lie about our children to prevent expanding on double click and to collap
se subproperties. | |
| 830 this.setExpandable(false); | |
| 831 this.listItemElement.classList.add('editing-sub-part'); | |
| 832 this.valueElement.classList.add('hidden'); | |
| 833 | |
| 834 this._prompt = new Components.ObjectPropertyPrompt(); | |
| 835 | |
| 836 var proxyElement = | |
| 837 this._prompt.attachAndStartEditing(this._editableDiv, this._editingCommi
tted.bind(this, originalContent)); | |
| 838 this.listItemElement.getComponentSelection().selectAllChildren(this._editabl
eDiv); | |
| 839 proxyElement.addEventListener('keydown', this._promptKeyDown.bind(this, orig
inalContent), false); | |
| 840 } | |
| 841 | |
| 842 _editingEnded() { | |
| 843 this._prompt.detach(); | |
| 844 delete this._prompt; | |
| 845 this._editableDiv.remove(); | |
| 846 this._updateExpandable(); | |
| 847 this.listItemElement.scrollLeft = 0; | |
| 848 this.listItemElement.classList.remove('editing-sub-part'); | |
| 849 } | |
| 850 | |
| 851 _editingCancelled() { | |
| 852 this.valueElement.classList.remove('hidden'); | |
| 853 this._editingEnded(); | |
| 854 } | |
| 855 | |
| 856 /** | |
| 857 * @param {string} originalContent | |
| 858 */ | |
| 859 _editingCommitted(originalContent) { | |
| 860 var userInput = this._prompt.text(); | |
| 861 if (userInput === originalContent) { | |
| 862 this._editingCancelled(); // nothing changed, so cancel | |
| 863 return; | |
| 864 } | |
| 865 | |
| 866 this._editingEnded(); | |
| 867 this._applyExpression(userInput); | |
| 868 } | |
| 869 | |
| 870 /** | |
| 871 * @param {string} originalContent | |
| 872 * @param {!Event} event | |
| 873 */ | |
| 874 _promptKeyDown(originalContent, event) { | |
| 875 if (isEnterKey(event)) { | |
| 876 event.consume(true); | |
| 877 this._editingCommitted(originalContent); | |
| 878 return; | |
| 879 } | |
| 880 if (event.key === 'Escape') { | |
| 881 event.consume(); | |
| 882 this._editingCancelled(); | |
| 883 return; | |
| 884 } | |
| 885 } | |
| 886 | |
| 887 /** | |
| 888 * @param {string} expression | |
| 889 */ | |
| 890 _applyExpression(expression) { | |
| 891 var property = SDK.RemoteObject.toCallArgument(this.property.symbol || this.
property.name); | |
| 892 expression = expression.trim(); | |
| 893 if (expression) | |
| 894 this.property.parentObject.setPropertyValue(property, expression, callback
.bind(this)); | |
| 895 else | |
| 896 this.property.parentObject.deleteProperty(property, callback.bind(this)); | |
| 897 | |
| 898 /** | |
| 899 * @param {?Protocol.Error} error | |
| 900 * @this {Components.ObjectPropertyTreeElement} | |
| 901 */ | |
| 902 function callback(error) { | |
| 903 if (error) { | |
| 904 this.update(); | |
| 905 return; | |
| 906 } | |
| 907 | |
| 908 if (!expression) { | |
| 909 // The property was deleted, so remove this tree element. | |
| 910 this.parent.removeChild(this); | |
| 911 } else { | |
| 912 // Call updateSiblings since their value might be based on the value tha
t just changed. | |
| 913 var parent = this.parent; | |
| 914 parent.invalidateChildren(); | |
| 915 parent.onpopulate(); | |
| 916 } | |
| 917 } | |
| 918 } | |
| 919 | |
| 920 /** | |
| 921 * @param {?SDK.RemoteObject} result | |
| 922 * @param {boolean=} wasThrown | |
| 923 */ | |
| 924 _onInvokeGetterClick(result, wasThrown) { | |
| 925 if (!result) | |
| 926 return; | |
| 927 this.property.value = result; | |
| 928 this.property.wasThrown = wasThrown; | |
| 929 | |
| 930 this.update(); | |
| 931 this.invalidateChildren(); | |
| 932 this._updateExpandable(); | |
| 933 } | |
| 934 | |
| 935 _updateExpandable() { | |
| 936 if (this.property.value) { | |
| 937 this.setExpandable( | |
| 938 !this.property.value.customPreview() && this.property.value.hasChildre
n && !this.property.wasThrown); | |
| 939 } else { | |
| 940 this.setExpandable(false); | |
| 941 } | |
| 942 } | |
| 943 }; | |
| 944 | |
| 945 | |
| 946 /** | |
| 947 * @unrestricted | |
| 948 */ | |
| 949 Components.ArrayGroupingTreeElement = class extends UI.TreeElement { | |
| 950 /** | |
| 951 * @param {!SDK.RemoteObject} object | |
| 952 * @param {number} fromIndex | |
| 953 * @param {number} toIndex | |
| 954 * @param {number} propertyCount | |
| 955 * @param {!Components.Linkifier=} linkifier | |
| 956 */ | |
| 957 constructor(object, fromIndex, toIndex, propertyCount, linkifier) { | |
| 958 super(String.sprintf('[%d \u2026 %d]', fromIndex, toIndex), true); | |
| 959 this.toggleOnClick = true; | |
| 960 this.selectable = false; | |
| 961 this._fromIndex = fromIndex; | |
| 962 this._toIndex = toIndex; | |
| 963 this._object = object; | |
| 964 this._readOnly = true; | |
| 965 this._propertyCount = propertyCount; | |
| 966 this._linkifier = linkifier; | |
| 967 } | |
| 968 | |
| 969 /** | |
| 970 * @param {!UI.TreeElement} treeNode | |
| 971 * @param {!SDK.RemoteObject} object | |
| 972 * @param {number} fromIndex | |
| 973 * @param {number} toIndex | |
| 974 * @param {!Components.Linkifier=} linkifier | |
| 975 */ | |
| 976 static _populateArray(treeNode, object, fromIndex, toIndex, linkifier) { | |
| 977 Components.ArrayGroupingTreeElement._populateRanges(treeNode, object, fromIn
dex, toIndex, true, linkifier); | |
| 978 } | |
| 979 | |
| 980 /** | |
| 981 * @param {!UI.TreeElement} treeNode | |
| 982 * @param {!SDK.RemoteObject} object | |
| 983 * @param {number} fromIndex | |
| 984 * @param {number} toIndex | |
| 985 * @param {boolean} topLevel | |
| 986 * @param {!Components.Linkifier=} linkifier | |
| 987 * @this {Components.ArrayGroupingTreeElement} | |
| 988 */ | |
| 989 static _populateRanges(treeNode, object, fromIndex, toIndex, topLevel, linkifi
er) { | |
| 990 object.callFunctionJSON( | |
| 991 packRanges, | |
| 992 [ | |
| 993 {value: fromIndex}, {value: toIndex}, {value: Components.ArrayGrouping
TreeElement._bucketThreshold}, | |
| 994 {value: Components.ArrayGroupingTreeElement._sparseIterationThreshold}
, | |
| 995 {value: Components.ArrayGroupingTreeElement._getOwnPropertyNamesThresh
old} | |
| 996 ], | |
| 997 callback); | |
| 998 | |
| 999 /** | |
| 1000 * Note: must declare params as optional. | |
| 1001 * @param {number=} fromIndex | |
| 1002 * @param {number=} toIndex | |
| 1003 * @param {number=} bucketThreshold | |
| 1004 * @param {number=} sparseIterationThreshold | |
| 1005 * @param {number=} getOwnPropertyNamesThreshold | |
| 1006 * @suppressReceiverCheck | |
| 1007 * @this {Object} | |
| 1008 */ | |
| 1009 function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThre
shold, getOwnPropertyNamesThreshold) { | |
| 1010 var ownPropertyNames = null; | |
| 1011 var consecutiveRange = (toIndex - fromIndex >= sparseIterationThreshold) &
& ArrayBuffer.isView(this); | |
| 1012 var skipGetOwnPropertyNames = consecutiveRange && (toIndex - fromIndex >=
getOwnPropertyNamesThreshold); | |
| 1013 | |
| 1014 function* arrayIndexes(object) { | |
| 1015 if (toIndex - fromIndex < sparseIterationThreshold) { | |
| 1016 for (var i = fromIndex; i <= toIndex; ++i) { | |
| 1017 if (i in object) | |
| 1018 yield i; | |
| 1019 } | |
| 1020 } else { | |
| 1021 ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(obje
ct); | |
| 1022 for (var i = 0; i < ownPropertyNames.length; ++i) { | |
| 1023 var name = ownPropertyNames[i]; | |
| 1024 var index = name >>> 0; | |
| 1025 if (('' + index) === name && fromIndex <= index && index <= toIndex) | |
| 1026 yield index; | |
| 1027 } | |
| 1028 } | |
| 1029 } | |
| 1030 | |
| 1031 var count = 0; | |
| 1032 if (consecutiveRange) { | |
| 1033 count = toIndex - fromIndex + 1; | |
| 1034 } else { | |
| 1035 for (var i of arrayIndexes(this)) | |
| 1036 ++count; | |
| 1037 } | |
| 1038 | |
| 1039 var bucketSize = count; | |
| 1040 if (count <= bucketThreshold) | |
| 1041 bucketSize = count; | |
| 1042 else | |
| 1043 bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.
log(bucketThreshold)) - 1); | |
| 1044 | |
| 1045 var ranges = []; | |
| 1046 if (consecutiveRange) { | |
| 1047 for (var i = fromIndex; i <= toIndex; i += bucketSize) { | |
| 1048 var groupStart = i; | |
| 1049 var groupEnd = groupStart + bucketSize - 1; | |
| 1050 if (groupEnd > toIndex) | |
| 1051 groupEnd = toIndex; | |
| 1052 ranges.push([groupStart, groupEnd, groupEnd - groupStart + 1]); | |
| 1053 } | |
| 1054 } else { | |
| 1055 count = 0; | |
| 1056 var groupStart = -1; | |
| 1057 var groupEnd = 0; | |
| 1058 for (var i of arrayIndexes(this)) { | |
| 1059 if (groupStart === -1) | |
| 1060 groupStart = i; | |
| 1061 groupEnd = i; | |
| 1062 if (++count === bucketSize) { | |
| 1063 ranges.push([groupStart, groupEnd, count]); | |
| 1064 count = 0; | |
| 1065 groupStart = -1; | |
| 1066 } | |
| 1067 } | |
| 1068 if (count > 0) | |
| 1069 ranges.push([groupStart, groupEnd, count]); | |
| 1070 } | |
| 1071 | |
| 1072 return {ranges: ranges, skipGetOwnPropertyNames: skipGetOwnPropertyNames}; | |
| 1073 } | |
| 1074 | |
| 1075 function callback(result) { | |
| 1076 if (!result) | |
| 1077 return; | |
| 1078 var ranges = /** @type {!Array.<!Array.<number>>} */ (result.ranges); | |
| 1079 if (ranges.length === 1) { | |
| 1080 Components.ArrayGroupingTreeElement._populateAsFragment( | |
| 1081 treeNode, object, ranges[0][0], ranges[0][1], linkifier); | |
| 1082 } else { | |
| 1083 for (var i = 0; i < ranges.length; ++i) { | |
| 1084 var fromIndex = ranges[i][0]; | |
| 1085 var toIndex = ranges[i][1]; | |
| 1086 var count = ranges[i][2]; | |
| 1087 if (fromIndex === toIndex) | |
| 1088 Components.ArrayGroupingTreeElement._populateAsFragment(treeNode, ob
ject, fromIndex, toIndex, linkifier); | |
| 1089 else | |
| 1090 treeNode.appendChild(new Components.ArrayGroupingTreeElement(object,
fromIndex, toIndex, count, linkifier)); | |
| 1091 } | |
| 1092 } | |
| 1093 if (topLevel) { | |
| 1094 Components.ArrayGroupingTreeElement._populateNonIndexProperties( | |
| 1095 treeNode, object, result.skipGetOwnPropertyNames, linkifier); | |
| 1096 } | |
| 1097 } | |
| 1098 } | |
| 1099 | |
| 1100 /** | |
| 1101 * @param {!UI.TreeElement} treeNode | |
| 1102 * @param {!SDK.RemoteObject} object | |
| 1103 * @param {number} fromIndex | |
| 1104 * @param {number} toIndex | |
| 1105 * @param {!Components.Linkifier=} linkifier | |
| 1106 * @this {Components.ArrayGroupingTreeElement} | |
| 1107 */ | |
| 1108 static _populateAsFragment(treeNode, object, fromIndex, toIndex, linkifier) { | |
| 1109 object.callFunction( | |
| 1110 buildArrayFragment, | |
| 1111 [{value: fromIndex}, {value: toIndex}, {value: Components.ArrayGroupingT
reeElement._sparseIterationThreshold}], | |
| 1112 processArrayFragment.bind(this)); | |
| 1113 | |
| 1114 /** | |
| 1115 * @suppressReceiverCheck | |
| 1116 * @this {Object} | |
| 1117 * @param {number=} fromIndex // must declare optional | |
| 1118 * @param {number=} toIndex // must declare optional | |
| 1119 * @param {number=} sparseIterationThreshold // must declare optional | |
| 1120 */ | |
| 1121 function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold) { | |
| 1122 var result = Object.create(null); | |
| 1123 if (toIndex - fromIndex < sparseIterationThreshold) { | |
| 1124 for (var i = fromIndex; i <= toIndex; ++i) { | |
| 1125 if (i in this) | |
| 1126 result[i] = this[i]; | |
| 1127 } | |
| 1128 } else { | |
| 1129 var ownPropertyNames = Object.getOwnPropertyNames(this); | |
| 1130 for (var i = 0; i < ownPropertyNames.length; ++i) { | |
| 1131 var name = ownPropertyNames[i]; | |
| 1132 var index = name >>> 0; | |
| 1133 if (String(index) === name && fromIndex <= index && index <= toIndex) | |
| 1134 result[index] = this[index]; | |
| 1135 } | |
| 1136 } | |
| 1137 return result; | |
| 1138 } | |
| 1139 | |
| 1140 /** | |
| 1141 * @param {?SDK.RemoteObject} arrayFragment | |
| 1142 * @param {boolean=} wasThrown | |
| 1143 * @this {Components.ArrayGroupingTreeElement} | |
| 1144 */ | |
| 1145 function processArrayFragment(arrayFragment, wasThrown) { | |
| 1146 if (!arrayFragment || wasThrown) | |
| 1147 return; | |
| 1148 arrayFragment.getAllProperties( | |
| 1149 false, Runtime.experiments.isEnabled('objectPreviews'), processPropert
ies.bind(this)); | |
| 1150 } | |
| 1151 | |
| 1152 /** @this {Components.ArrayGroupingTreeElement} */ | |
| 1153 function processProperties(properties, internalProperties) { | |
| 1154 if (!properties) | |
| 1155 return; | |
| 1156 | |
| 1157 properties.sort(Components.ObjectPropertiesSection.CompareProperties); | |
| 1158 for (var i = 0; i < properties.length; ++i) { | |
| 1159 properties[i].parentObject = this._object; | |
| 1160 var childTreeElement = new Components.ObjectPropertyTreeElement(properti
es[i], linkifier); | |
| 1161 childTreeElement._readOnly = true; | |
| 1162 treeNode.appendChild(childTreeElement); | |
| 1163 } | |
| 1164 } | |
| 1165 } | |
| 1166 | |
| 1167 /** | |
| 1168 * @param {!UI.TreeElement} treeNode | |
| 1169 * @param {!SDK.RemoteObject} object | |
| 1170 * @param {boolean} skipGetOwnPropertyNames | |
| 1171 * @param {!Components.Linkifier=} linkifier | |
| 1172 * @this {Components.ArrayGroupingTreeElement} | |
| 1173 */ | |
| 1174 static _populateNonIndexProperties(treeNode, object, skipGetOwnPropertyNames,
linkifier) { | |
| 1175 object.callFunction(buildObjectFragment, [{value: skipGetOwnPropertyNames}],
processObjectFragment.bind(this)); | |
| 1176 | |
| 1177 /** | |
| 1178 * @param {boolean=} skipGetOwnPropertyNames | |
| 1179 * @suppressReceiverCheck | |
| 1180 * @this {Object} | |
| 1181 */ | |
| 1182 function buildObjectFragment(skipGetOwnPropertyNames) { | |
| 1183 var result = {__proto__: this.__proto__}; | |
| 1184 if (skipGetOwnPropertyNames) | |
| 1185 return result; | |
| 1186 var names = Object.getOwnPropertyNames(this); | |
| 1187 for (var i = 0; i < names.length; ++i) { | |
| 1188 var name = names[i]; | |
| 1189 // Array index check according to the ES5-15.4. | |
| 1190 if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff) | |
| 1191 continue; | |
| 1192 var descriptor = Object.getOwnPropertyDescriptor(this, name); | |
| 1193 if (descriptor) | |
| 1194 Object.defineProperty(result, name, descriptor); | |
| 1195 } | |
| 1196 return result; | |
| 1197 } | |
| 1198 | |
| 1199 /** | |
| 1200 * @param {?SDK.RemoteObject} arrayFragment | |
| 1201 * @param {boolean=} wasThrown | |
| 1202 * @this {Components.ArrayGroupingTreeElement} | |
| 1203 */ | |
| 1204 function processObjectFragment(arrayFragment, wasThrown) { | |
| 1205 if (!arrayFragment || wasThrown) | |
| 1206 return; | |
| 1207 arrayFragment.getOwnProperties(Runtime.experiments.isEnabled('objectPrevie
ws'), processProperties.bind(this)); | |
| 1208 } | |
| 1209 | |
| 1210 /** | |
| 1211 * @param {?Array.<!SDK.RemoteObjectProperty>} properties | |
| 1212 * @param {?Array.<!SDK.RemoteObjectProperty>=} internalProperties | |
| 1213 * @this {Components.ArrayGroupingTreeElement} | |
| 1214 */ | |
| 1215 function processProperties(properties, internalProperties) { | |
| 1216 if (!properties) | |
| 1217 return; | |
| 1218 properties.sort(Components.ObjectPropertiesSection.CompareProperties); | |
| 1219 for (var i = 0; i < properties.length; ++i) { | |
| 1220 properties[i].parentObject = this._object; | |
| 1221 var childTreeElement = new Components.ObjectPropertyTreeElement(properti
es[i], linkifier); | |
| 1222 childTreeElement._readOnly = true; | |
| 1223 treeNode.appendChild(childTreeElement); | |
| 1224 } | |
| 1225 } | |
| 1226 } | |
| 1227 | |
| 1228 /** | |
| 1229 * @override | |
| 1230 */ | |
| 1231 onpopulate() { | |
| 1232 if (this._propertyCount >= Components.ArrayGroupingTreeElement._bucketThresh
old) { | |
| 1233 Components.ArrayGroupingTreeElement._populateRanges( | |
| 1234 this, this._object, this._fromIndex, this._toIndex, false, this._linki
fier); | |
| 1235 return; | |
| 1236 } | |
| 1237 Components.ArrayGroupingTreeElement._populateAsFragment( | |
| 1238 this, this._object, this._fromIndex, this._toIndex, this._linkifier); | |
| 1239 } | |
| 1240 | |
| 1241 /** | |
| 1242 * @override | |
| 1243 */ | |
| 1244 onattach() { | |
| 1245 this.listItemElement.classList.add('object-properties-section-name'); | |
| 1246 } | |
| 1247 }; | |
| 1248 | |
| 1249 Components.ArrayGroupingTreeElement._bucketThreshold = 100; | |
| 1250 Components.ArrayGroupingTreeElement._sparseIterationThreshold = 250000; | |
| 1251 Components.ArrayGroupingTreeElement._getOwnPropertyNamesThreshold = 500000; | |
| 1252 | |
| 1253 | |
| 1254 /** | |
| 1255 * @unrestricted | |
| 1256 */ | |
| 1257 Components.ObjectPropertyPrompt = class extends UI.TextPrompt { | |
| 1258 constructor() { | |
| 1259 super(); | |
| 1260 this.initialize(Components.JavaScriptAutocomplete.completionsForTextInCurren
tContext); | |
| 1261 } | |
| 1262 }; | |
| 1263 | |
| 1264 /** | |
| 1265 * @unrestricted | |
| 1266 */ | |
| 1267 Components.ObjectPropertiesSectionExpandController = class { | |
| 1268 constructor() { | |
| 1269 /** @type {!Set.<string>} */ | |
| 1270 this._expandedProperties = new Set(); | |
| 1271 } | |
| 1272 | |
| 1273 /** | |
| 1274 * @param {string} id | |
| 1275 * @param {!Components.ObjectPropertiesSection} section | |
| 1276 */ | |
| 1277 watchSection(id, section) { | |
| 1278 section.addEventListener(UI.TreeOutline.Events.ElementAttached, this._elemen
tAttached, this); | |
| 1279 section.addEventListener(UI.TreeOutline.Events.ElementExpanded, this._elemen
tExpanded, this); | |
| 1280 section.addEventListener(UI.TreeOutline.Events.ElementCollapsed, this._eleme
ntCollapsed, this); | |
| 1281 section[Components.ObjectPropertiesSectionExpandController._treeOutlineId] =
id; | |
| 1282 | |
| 1283 if (this._expandedProperties.has(id)) | |
| 1284 section.expand(); | |
| 1285 } | |
| 1286 | |
| 1287 /** | |
| 1288 * @param {string} id | |
| 1289 */ | |
| 1290 stopWatchSectionsWithId(id) { | |
| 1291 for (var property of this._expandedProperties) { | |
| 1292 if (property.startsWith(id + ':')) | |
| 1293 this._expandedProperties.delete(property); | |
| 1294 } | |
| 1295 } | |
| 1296 | |
| 1297 /** | |
| 1298 * @param {!Common.Event} event | |
| 1299 */ | |
| 1300 _elementAttached(event) { | |
| 1301 var element = /** @type {!UI.TreeElement} */ (event.data); | |
| 1302 if (element.isExpandable() && this._expandedProperties.has(this._propertyPat
h(element))) | |
| 1303 element.expand(); | |
| 1304 } | |
| 1305 | |
| 1306 /** | |
| 1307 * @param {!Common.Event} event | |
| 1308 */ | |
| 1309 _elementExpanded(event) { | |
| 1310 var element = /** @type {!UI.TreeElement} */ (event.data); | |
| 1311 this._expandedProperties.add(this._propertyPath(element)); | |
| 1312 } | |
| 1313 | |
| 1314 /** | |
| 1315 * @param {!Common.Event} event | |
| 1316 */ | |
| 1317 _elementCollapsed(event) { | |
| 1318 var element = /** @type {!UI.TreeElement} */ (event.data); | |
| 1319 this._expandedProperties.delete(this._propertyPath(element)); | |
| 1320 } | |
| 1321 | |
| 1322 /** | |
| 1323 * @param {!UI.TreeElement} treeElement | |
| 1324 * @return {string} | |
| 1325 */ | |
| 1326 _propertyPath(treeElement) { | |
| 1327 var cachedPropertyPath = treeElement[Components.ObjectPropertiesSectionExpan
dController._cachedPathSymbol]; | |
| 1328 if (cachedPropertyPath) | |
| 1329 return cachedPropertyPath; | |
| 1330 | |
| 1331 var current = treeElement; | |
| 1332 var rootElement = treeElement.treeOutline.objectTreeElement(); | |
| 1333 | |
| 1334 var result; | |
| 1335 | |
| 1336 while (current !== rootElement) { | |
| 1337 var currentName = ''; | |
| 1338 if (current.property) | |
| 1339 currentName = current.property.name; | |
| 1340 else | |
| 1341 currentName = typeof current.title === 'string' ? current.title : curren
t.title.textContent; | |
| 1342 | |
| 1343 result = currentName + (result ? '.' + result : ''); | |
| 1344 current = current.parent; | |
| 1345 } | |
| 1346 var treeOutlineId = treeElement.treeOutline[Components.ObjectPropertiesSecti
onExpandController._treeOutlineId]; | |
| 1347 result = treeOutlineId + (result ? ':' + result : ''); | |
| 1348 treeElement[Components.ObjectPropertiesSectionExpandController._cachedPathSy
mbol] = result; | |
| 1349 return result; | |
| 1350 } | |
| 1351 }; | |
| 1352 | |
| 1353 Components.ObjectPropertiesSectionExpandController._cachedPathSymbol = Symbol('c
achedPath'); | |
| 1354 Components.ObjectPropertiesSectionExpandController._treeOutlineId = Symbol('tree
OutlineId'); | |
| OLD | NEW |