OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | |
5 /** | 4 /** |
6 * @constructor | 5 * @unrestricted |
7 * @param {!WebInspector.RemoteObject} object | |
8 */ | 6 */ |
9 WebInspector.CustomPreviewSection = function(object) | 7 WebInspector.CustomPreviewSection = class { |
10 { | 8 /** |
11 this._sectionElement = createElementWithClass("span", "custom-expandable-sec
tion"); | 9 * @param {!WebInspector.RemoteObject} object |
| 10 */ |
| 11 constructor(object) { |
| 12 this._sectionElement = createElementWithClass('span', 'custom-expandable-sec
tion'); |
12 this._object = object; | 13 this._object = object; |
13 this._expanded = false; | 14 this._expanded = false; |
14 this._cachedContent = null; | 15 this._cachedContent = null; |
15 var customPreview = object.customPreview(); | 16 var customPreview = object.customPreview(); |
16 | 17 |
17 try { | 18 try { |
18 var headerJSON = JSON.parse(customPreview.header); | 19 var headerJSON = JSON.parse(customPreview.header); |
19 } catch (e) { | 20 } catch (e) { |
20 WebInspector.console.error("Broken formatter: header is invalid json " +
e); | 21 WebInspector.console.error('Broken formatter: header is invalid json ' + e
); |
21 return; | 22 return; |
22 } | 23 } |
23 this._header = this._renderJSONMLTag(headerJSON); | 24 this._header = this._renderJSONMLTag(headerJSON); |
24 if (this._header.nodeType === Node.TEXT_NODE) { | 25 if (this._header.nodeType === Node.TEXT_NODE) { |
25 WebInspector.console.error("Broken formatter: header should be an elemen
t node."); | 26 WebInspector.console.error('Broken formatter: header should be an element
node.'); |
| 27 return; |
| 28 } |
| 29 |
| 30 if (customPreview.hasBody) { |
| 31 this._header.classList.add('custom-expandable-section-header'); |
| 32 this._header.addEventListener('click', this._onClick.bind(this), false); |
| 33 } |
| 34 |
| 35 this._sectionElement.appendChild(this._header); |
| 36 } |
| 37 |
| 38 /** |
| 39 * @return {!Element} |
| 40 */ |
| 41 element() { |
| 42 return this._sectionElement; |
| 43 } |
| 44 |
| 45 /** |
| 46 * @param {*} jsonML |
| 47 * @return {!Node} |
| 48 */ |
| 49 _renderJSONMLTag(jsonML) { |
| 50 if (!Array.isArray(jsonML)) |
| 51 return createTextNode(jsonML + ''); |
| 52 |
| 53 var array = /** @type {!Array.<*>} */ (jsonML); |
| 54 if (array[0] === 'object') |
| 55 return this._layoutObjectTag(array); |
| 56 else |
| 57 return this._renderElement(array); |
| 58 } |
| 59 |
| 60 /** |
| 61 * |
| 62 * @param {!Array.<*>} object |
| 63 * @return {!Node} |
| 64 */ |
| 65 _renderElement(object) { |
| 66 var tagName = object.shift(); |
| 67 if (!WebInspector.CustomPreviewSection._tagsWhiteList.has(tagName)) { |
| 68 WebInspector.console.error('Broken formatter: element ' + tagName + ' is n
ot allowed!'); |
| 69 return createElement('span'); |
| 70 } |
| 71 var element = createElement(/** @type {string} */ (tagName)); |
| 72 if ((typeof object[0] === 'object') && !Array.isArray(object[0])) { |
| 73 var attributes = object.shift(); |
| 74 for (var key in attributes) { |
| 75 var value = attributes[key]; |
| 76 if ((key !== 'style') || (typeof value !== 'string')) |
| 77 continue; |
| 78 |
| 79 element.setAttribute(key, value); |
| 80 } |
| 81 } |
| 82 |
| 83 this._appendJsonMLTags(element, object); |
| 84 return element; |
| 85 } |
| 86 |
| 87 /** |
| 88 * @param {!Array.<*>} objectTag |
| 89 * @return {!Node} |
| 90 */ |
| 91 _layoutObjectTag(objectTag) { |
| 92 objectTag.shift(); |
| 93 var attributes = objectTag.shift(); |
| 94 var remoteObject = |
| 95 this._object.target().runtimeModel.createRemoteObject(/** @type {!Runtim
eAgent.RemoteObject} */ (attributes)); |
| 96 if (remoteObject.customPreview()) |
| 97 return (new WebInspector.CustomPreviewSection(remoteObject)).element(); |
| 98 |
| 99 var sectionElement = WebInspector.ObjectPropertiesSection.defaultObjectPrese
ntation(remoteObject); |
| 100 sectionElement.classList.toggle('custom-expandable-section-standard-section'
, remoteObject.hasChildren); |
| 101 return sectionElement; |
| 102 } |
| 103 |
| 104 /** |
| 105 * @param {!Node} parentElement |
| 106 * @param {!Array.<*>} jsonMLTags |
| 107 */ |
| 108 _appendJsonMLTags(parentElement, jsonMLTags) { |
| 109 for (var i = 0; i < jsonMLTags.length; ++i) |
| 110 parentElement.appendChild(this._renderJSONMLTag(jsonMLTags[i])); |
| 111 } |
| 112 |
| 113 /** |
| 114 * @param {!Event} event |
| 115 */ |
| 116 _onClick(event) { |
| 117 event.consume(true); |
| 118 if (this._cachedContent) |
| 119 this._toggleExpand(); |
| 120 else |
| 121 this._loadBody(); |
| 122 } |
| 123 |
| 124 _toggleExpand() { |
| 125 this._expanded = !this._expanded; |
| 126 this._header.classList.toggle('expanded', this._expanded); |
| 127 this._cachedContent.classList.toggle('hidden', !this._expanded); |
| 128 } |
| 129 |
| 130 _loadBody() { |
| 131 /** |
| 132 * @suppressReceiverCheck |
| 133 * @suppressGlobalPropertiesCheck |
| 134 * @suppress {undefinedVars} |
| 135 * @this {Object} |
| 136 * @param {function(!Object, *):*} bindRemoteObject |
| 137 * @param {*=} formatter |
| 138 * @param {*=} config |
| 139 */ |
| 140 function load(bindRemoteObject, formatter, config) { |
| 141 /** |
| 142 * @param {*} jsonMLObject |
| 143 * @throws {string} error message |
| 144 */ |
| 145 function substituteObjectTagsInCustomPreview(jsonMLObject) { |
| 146 if (!jsonMLObject || (typeof jsonMLObject !== 'object') || (typeof jsonM
LObject.splice !== 'function')) |
| 147 return; |
| 148 |
| 149 var obj = jsonMLObject.length; |
| 150 if (!(typeof obj === 'number' && obj >>> 0 === obj && (obj > 0 || 1 / ob
j > 0))) |
| 151 return; |
| 152 |
| 153 var startIndex = 1; |
| 154 if (jsonMLObject[0] === 'object') { |
| 155 var attributes = jsonMLObject[1]; |
| 156 var originObject = attributes['object']; |
| 157 var config = attributes['config']; |
| 158 if (typeof originObject === 'undefined') |
| 159 throw 'Illegal format: obligatory attribute "object" isn\'t specifie
d'; |
| 160 |
| 161 jsonMLObject[1] = bindRemoteObject(originObject, config); |
| 162 startIndex = 2; |
| 163 } |
| 164 for (var i = startIndex; i < jsonMLObject.length; ++i) |
| 165 substituteObjectTagsInCustomPreview(jsonMLObject[i]); |
| 166 } |
| 167 |
| 168 try { |
| 169 var body = formatter.body(this, config); |
| 170 substituteObjectTagsInCustomPreview(body); |
| 171 return body; |
| 172 } catch (e) { |
| 173 console.error('Custom Formatter Failed: ' + e); |
| 174 return null; |
| 175 } |
| 176 } |
| 177 |
| 178 var customPreview = this._object.customPreview(); |
| 179 var args = [{objectId: customPreview.bindRemoteObjectFunctionId}, {objectId:
customPreview.formatterObjectId}]; |
| 180 if (customPreview.configObjectId) |
| 181 args.push({objectId: customPreview.configObjectId}); |
| 182 this._object.callFunctionJSON(load, args, onBodyLoaded.bind(this)); |
| 183 |
| 184 /** |
| 185 * @param {*} bodyJsonML |
| 186 * @this {WebInspector.CustomPreviewSection} |
| 187 */ |
| 188 function onBodyLoaded(bodyJsonML) { |
| 189 if (!bodyJsonML) |
26 return; | 190 return; |
27 } | 191 |
28 | 192 this._cachedContent = this._renderJSONMLTag(bodyJsonML); |
29 if (customPreview.hasBody) { | 193 this._sectionElement.appendChild(this._cachedContent); |
30 this._header.classList.add("custom-expandable-section-header"); | 194 this._toggleExpand(); |
31 this._header.addEventListener("click", this._onClick.bind(this), false); | 195 } |
32 } | 196 } |
33 | |
34 this._sectionElement.appendChild(this._header); | |
35 }; | 197 }; |
36 | 198 |
37 /** | 199 /** |
38 * @constructor | 200 * @unrestricted |
39 * @param {!WebInspector.RemoteObject} object | |
40 */ | 201 */ |
41 WebInspector.CustomPreviewComponent = function(object) | 202 WebInspector.CustomPreviewComponent = class { |
42 { | 203 /** |
| 204 * @param {!WebInspector.RemoteObject} object |
| 205 */ |
| 206 constructor(object) { |
43 this._object = object; | 207 this._object = object; |
44 this._customPreviewSection = new WebInspector.CustomPreviewSection(object); | 208 this._customPreviewSection = new WebInspector.CustomPreviewSection(object); |
45 this.element = createElementWithClass("span", "source-code"); | 209 this.element = createElementWithClass('span', 'source-code'); |
46 var shadowRoot = WebInspector.createShadowRootWithCoreStyles(this.element, "
components/customPreviewSection.css"); | 210 var shadowRoot = WebInspector.createShadowRootWithCoreStyles(this.element, '
components/customPreviewSection.css'); |
47 this.element.addEventListener("contextmenu", this._contextMenuEventFired.bin
d(this), false); | 211 this.element.addEventListener('contextmenu', this._contextMenuEventFired.bin
d(this), false); |
48 shadowRoot.appendChild(this._customPreviewSection.element()); | 212 shadowRoot.appendChild(this._customPreviewSection.element()); |
| 213 } |
| 214 |
| 215 expandIfPossible() { |
| 216 if (this._object.customPreview().hasBody && this._customPreviewSection) |
| 217 this._customPreviewSection._loadBody(); |
| 218 } |
| 219 |
| 220 /** |
| 221 * @param {!Event} event |
| 222 */ |
| 223 _contextMenuEventFired(event) { |
| 224 var contextMenu = new WebInspector.ContextMenu(event); |
| 225 if (this._customPreviewSection) |
| 226 contextMenu.appendItem( |
| 227 WebInspector.UIString.capitalize('Show as Javascript ^object'), this._
disassemble.bind(this)); |
| 228 contextMenu.appendApplicableItems(this._object); |
| 229 contextMenu.show(); |
| 230 } |
| 231 |
| 232 _disassemble() { |
| 233 this.element.shadowRoot.textContent = ''; |
| 234 this._customPreviewSection = null; |
| 235 this.element.shadowRoot.appendChild(WebInspector.ObjectPropertiesSection.def
aultObjectPresentation(this._object)); |
| 236 } |
49 }; | 237 }; |
50 | 238 |
51 WebInspector.CustomPreviewComponent.prototype = { | 239 WebInspector.CustomPreviewSection._tagsWhiteList = new Set(['span', 'div', 'ol',
'li', 'table', 'tr', 'td']); |
52 expandIfPossible: function() | |
53 { | |
54 if (this._object.customPreview().hasBody && this._customPreviewSection) | |
55 this._customPreviewSection._loadBody(); | |
56 }, | |
57 | |
58 /** | |
59 * @param {!Event} event | |
60 */ | |
61 _contextMenuEventFired: function(event) | |
62 { | |
63 var contextMenu = new WebInspector.ContextMenu(event); | |
64 if (this._customPreviewSection) | |
65 contextMenu.appendItem(WebInspector.UIString.capitalize("Show as Jav
ascript ^object"), this._disassemble.bind(this)); | |
66 contextMenu.appendApplicableItems(this._object); | |
67 contextMenu.show(); | |
68 }, | |
69 | |
70 _disassemble: function() | |
71 { | |
72 this.element.shadowRoot.textContent = ""; | |
73 this._customPreviewSection = null; | |
74 this.element.shadowRoot.appendChild(WebInspector.ObjectPropertiesSection
.defaultObjectPresentation(this._object)); | |
75 } | |
76 }; | |
77 | |
78 WebInspector.CustomPreviewSection._tagsWhiteList = new Set(["span", "div", "ol",
"li","table", "tr", "td"]); | |
79 | |
80 WebInspector.CustomPreviewSection.prototype = { | |
81 | |
82 /** | |
83 * @return {!Element} | |
84 */ | |
85 element: function() | |
86 { | |
87 return this._sectionElement; | |
88 }, | |
89 | |
90 /** | |
91 * @param {*} jsonML | |
92 * @return {!Node} | |
93 */ | |
94 _renderJSONMLTag: function(jsonML) | |
95 { | |
96 if (!Array.isArray(jsonML)) | |
97 return createTextNode(jsonML + ""); | |
98 | |
99 var array = /** @type {!Array.<*>} */(jsonML); | |
100 if (array[0] === "object") | |
101 return this._layoutObjectTag(array); | |
102 else | |
103 return this._renderElement(array); | |
104 }, | |
105 | |
106 /** | |
107 * | |
108 * @param {!Array.<*>} object | |
109 * @return {!Node} | |
110 */ | |
111 _renderElement: function(object) | |
112 { | |
113 var tagName = object.shift(); | |
114 if (!WebInspector.CustomPreviewSection._tagsWhiteList.has(tagName)) { | |
115 WebInspector.console.error("Broken formatter: element " + tagName +
" is not allowed!"); | |
116 return createElement("span"); | |
117 } | |
118 var element = createElement(/** @type {string} */ (tagName)); | |
119 if ((typeof object[0] === "object") && !Array.isArray(object[0])) { | |
120 var attributes = object.shift(); | |
121 for (var key in attributes) { | |
122 var value = attributes[key]; | |
123 if ((key !== "style") || (typeof value !== "string")) | |
124 continue; | |
125 | |
126 element.setAttribute(key, value); | |
127 } | |
128 } | |
129 | |
130 this._appendJsonMLTags(element, object); | |
131 return element; | |
132 }, | |
133 | |
134 /** | |
135 * @param {!Array.<*>} objectTag | |
136 * @return {!Node} | |
137 */ | |
138 _layoutObjectTag: function(objectTag) | |
139 { | |
140 objectTag.shift(); | |
141 var attributes = objectTag.shift(); | |
142 var remoteObject = this._object.target().runtimeModel.createRemoteObject
(/** @type {!RuntimeAgent.RemoteObject} */ (attributes)); | |
143 if (remoteObject.customPreview()) | |
144 return (new WebInspector.CustomPreviewSection(remoteObject)).element
(); | |
145 | |
146 var sectionElement = WebInspector.ObjectPropertiesSection.defaultObjectP
resentation(remoteObject); | |
147 sectionElement.classList.toggle("custom-expandable-section-standard-sect
ion", remoteObject.hasChildren); | |
148 return sectionElement; | |
149 }, | |
150 | |
151 /** | |
152 * @param {!Node} parentElement | |
153 * @param {!Array.<*>} jsonMLTags | |
154 */ | |
155 _appendJsonMLTags: function(parentElement, jsonMLTags) | |
156 { | |
157 for (var i = 0; i < jsonMLTags.length; ++i) | |
158 parentElement.appendChild(this._renderJSONMLTag(jsonMLTags[i])); | |
159 }, | |
160 | |
161 /** | |
162 * @param {!Event} event | |
163 */ | |
164 _onClick: function(event) | |
165 { | |
166 event.consume(true); | |
167 if (this._cachedContent) | |
168 this._toggleExpand(); | |
169 else | |
170 this._loadBody(); | |
171 }, | |
172 | |
173 _toggleExpand: function() | |
174 { | |
175 this._expanded = !this._expanded; | |
176 this._header.classList.toggle("expanded", this._expanded); | |
177 this._cachedContent.classList.toggle("hidden", !this._expanded); | |
178 }, | |
179 | |
180 _loadBody: function() | |
181 { | |
182 /** | |
183 * @suppressReceiverCheck | |
184 * @suppressGlobalPropertiesCheck | |
185 * @suppress {undefinedVars} | |
186 * @this {Object} | |
187 * @param {function(!Object, *):*} bindRemoteObject | |
188 * @param {*=} formatter | |
189 * @param {*=} config | |
190 */ | |
191 function load(bindRemoteObject, formatter, config) | |
192 { | |
193 /** | |
194 * @param {*} jsonMLObject | |
195 * @throws {string} error message | |
196 */ | |
197 function substituteObjectTagsInCustomPreview(jsonMLObject) | |
198 { | |
199 if (!jsonMLObject || (typeof jsonMLObject !== "object") || (type
of jsonMLObject.splice !== "function")) | |
200 return; | |
201 | |
202 var obj = jsonMLObject.length; | |
203 if (!(typeof obj === "number" && obj >>> 0 === obj && (obj > 0 |
| 1 / obj > 0))) | |
204 return; | |
205 | |
206 var startIndex = 1; | |
207 if (jsonMLObject[0] === "object") { | |
208 var attributes = jsonMLObject[1]; | |
209 var originObject = attributes["object"]; | |
210 var config = attributes["config"]; | |
211 if (typeof originObject === "undefined") | |
212 throw "Illegal format: obligatory attribute \"object\" i
sn't specified"; | |
213 | |
214 jsonMLObject[1] = bindRemoteObject(originObject, config); | |
215 startIndex = 2; | |
216 } | |
217 for (var i = startIndex; i < jsonMLObject.length; ++i) | |
218 substituteObjectTagsInCustomPreview(jsonMLObject[i]); | |
219 } | |
220 | |
221 try { | |
222 var body = formatter.body(this, config); | |
223 substituteObjectTagsInCustomPreview(body); | |
224 return body; | |
225 } catch (e) { | |
226 console.error("Custom Formatter Failed: " + e); | |
227 return null; | |
228 } | |
229 } | |
230 | |
231 var customPreview = this._object.customPreview(); | |
232 var args = [{objectId: customPreview.bindRemoteObjectFunctionId}, {objec
tId: customPreview.formatterObjectId}]; | |
233 if (customPreview.configObjectId) | |
234 args.push({objectId: customPreview.configObjectId}); | |
235 this._object.callFunctionJSON(load, args, onBodyLoaded.bind(this)); | |
236 | |
237 /** | |
238 * @param {*} bodyJsonML | |
239 * @this {WebInspector.CustomPreviewSection} | |
240 */ | |
241 function onBodyLoaded(bodyJsonML) | |
242 { | |
243 if (!bodyJsonML) | |
244 return; | |
245 | |
246 this._cachedContent = this._renderJSONMLTag(bodyJsonML); | |
247 this._sectionElement.appendChild(this._cachedContent); | |
248 this._toggleExpand(); | |
249 } | |
250 } | |
251 }; | |
OLD | NEW |