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 |