1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/lib/domplate", 7 "firebug/lib/locale", 8 "firebug/lib/events", 9 "firebug/lib/css", 10 "firebug/lib/dom", 11 "firebug/lib/http", 12 "firebug/lib/string", 13 "firebug/lib/json", 14 "firebug/dom/toggleBranch", 15 "firebug/lib/array", 16 "firebug/lib/system", 17 "firebug/dom/domPanel", 18 "firebug/chrome/reps" 19 ], 20 function(Obj, Firebug, Domplate, Locale, Events, Css, Dom, Http, Str, Json, 21 ToggleBranch, Arr, System) { 22 23 // ********************************************************************************************* // 24 25 // List of JSON content types. 26 var contentTypes = 27 { 28 "text/plain": 1, 29 "text/javascript": 1, 30 "text/x-javascript": 1, 31 "text/json": 1, 32 "text/x-json": 1, 33 "application/json": 1, 34 "application/x-json": 1, 35 "application/javascript": 1, 36 "application/x-javascript": 1, 37 "application/json-rpc": 1 38 }; 39 40 // ********************************************************************************************* // 41 // Model implementation 42 43 Firebug.JSONViewerModel = Obj.extend(Firebug.Module, 44 { 45 dispatchName: "jsonViewer", 46 contentTypes: contentTypes, 47 48 initialize: function() 49 { 50 Firebug.NetMonitor.NetInfoBody.addListener(this); 51 Firebug.registerUIListener(this); 52 }, 53 54 shutdown: function() 55 { 56 Firebug.NetMonitor.NetInfoBody.removeListener(this); 57 Firebug.unregisterUIListener(this); 58 }, 59 60 onContextMenu: function(items, object, target, context, panel, popup) 61 { 62 if (panel.name != "net" && panel.name != "console") 63 return; 64 65 var memberLabel = Dom.getAncestorByClass(target, "memberLabel"); 66 67 if (!memberLabel) 68 return; 69 70 var row = Dom.getAncestorByClass(target, "memberRow"); 71 if (!row || !row.domObject.value) 72 return; 73 74 items.push({ 75 id: "fbNetCopyJSON", 76 nol10n: true, 77 label: Locale.$STRF("net.jsonviewer.Copy_JSON", [row.domObject.name]), 78 command: Obj.bindFixed(this.copyJsonResponse, this, row) 79 }); 80 }, 81 82 copyJsonResponse:function(row) 83 { 84 var value = JSON.stringify(row.domObject.value); 85 if (value) 86 System.copyToClipboard(value); 87 }, 88 89 initTabBody: function(infoBox, file) 90 { 91 if (FBTrace.DBG_JSONVIEWER) 92 FBTrace.sysout("jsonviewer.initTabBody", {infoBox: infoBox, file: file}); 93 94 // Let listeners to parse the JSON. 95 Events.dispatch(this.fbListeners, "onParseJSON", [file]); 96 97 // The JSON is still no there, try to parse most common cases. 98 if (!file.jsonObject) 99 { 100 if (this.isJSON(Http.safeGetContentType(file.request), file.responseText)) 101 file.jsonObject = this.parseJSON(file); 102 } 103 104 // The jsonObject is created so, the JSON tab can be displayed. 105 if (file.jsonObject && Obj.hasProperties(file.jsonObject)) 106 { 107 Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "JSON", 108 Locale.$STR("jsonviewer.tab.JSON")); 109 110 if (FBTrace.DBG_JSONVIEWER) 111 FBTrace.sysout("jsonviewer.initTabBody; JSON object available " + 112 (typeof(file.jsonObject) != "undefined"), file.jsonObject); 113 } 114 }, 115 116 isJSON: function(contentType, data) 117 { 118 // Workaround for JSON responses without proper content type 119 // Let's consider all responses starting with "{" as JSON. In the worst 120 // case there will be an exception when parsing. This means that no-JSON 121 // responses (and post data) (with "{") can be parsed unnecessarily, 122 // which represents a little overhead, but this happens only if the request 123 // is actually expanded by the user in the UI (Net & Console panels). 124 var responseText = data ? Str.trimLeft(data) : null; 125 if (responseText && responseText.charAt(0) === "{") 126 return true; 127 128 if (!contentType) 129 return false; 130 131 contentType = contentType.split(";")[0]; 132 contentType = Str.trim(contentType); 133 return contentTypes[contentType]; 134 }, 135 136 // Update listener for TabView 137 updateTabBody: function(infoBox, file, context) 138 { 139 var tab = infoBox.selectedTab; 140 var tabBody = infoBox.getElementsByClassName("netInfoJSONText").item(0); 141 if (!Css.hasClass(tab, "netInfoJSONTab") || tabBody.updated) 142 return; 143 144 if (FBTrace.DBG_JSONVIEWER) 145 FBTrace.sysout("jsonviewer.updateTabBody", infoBox); 146 147 tabBody.updated = true; 148 tabBody.context = context; 149 150 this.Preview.render(tabBody, file, context); 151 }, 152 153 parseJSON: function(file) 154 { 155 var jsonString = new String(file.responseText); 156 return Json.parseJSONString(jsonString, "http://" + file.request.originalURI.host); 157 }, 158 }); 159 160 // ********************************************************************************************* // 161 162 with (Domplate) { 163 Firebug.JSONViewerModel.Preview = domplate( 164 { 165 bodyTag: 166 DIV({"class": "jsonPreview", _repObject: "$file"}, 167 DIV({"class": "title"}, 168 DIV({"class": "sortLink", onclick: "$onSort", $sorted: "$sorted"}, 169 SPAN({"class": "doSort"}, Locale.$STR("jsonviewer.sort")), 170 SPAN({"class": "doNotSort"}, Locale.$STR("jsonviewer.do not sort")) 171 ) 172 ), 173 DIV({"class": "jsonPreviewBody"}) 174 ), 175 176 onSort: function(event) 177 { 178 var target = event.target; 179 var sortLink = Dom.getAncestorByClass(target, "sortLink"); 180 if (!sortLink) 181 return; 182 183 Events.cancelEvent(event); 184 185 Css.toggleClass(sortLink, "sorted"); 186 Firebug.Options.set("sortJsonPreview", !Firebug.sortJsonPreview); 187 188 var preview = Dom.getAncestorByClass(sortLink, "jsonPreview"); 189 var body = Dom.getAncestorByClass(sortLink, "netInfoJSONText"); 190 if (!body) 191 { 192 if (FBTrace.DBG_ERRORS) 193 FBTrace.sysout("jsonViewer.onSort; ERROR body is null"); 194 return; 195 } 196 197 this.render(body, preview.repObject, body.context); 198 }, 199 200 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 201 202 render: function(body, file, context) 203 { 204 if (!file.jsonObject) 205 return; 206 207 if (!body.jsonTree) 208 body.jsonTree = new JSONTreePlate(); 209 210 var input = {file: file, sorted: Firebug.sortJsonPreview}; 211 parentNode = this.bodyTag.replace(input, body, this); 212 parentNode = parentNode.getElementsByClassName("jsonPreviewBody").item(0); 213 214 body.jsonTree.render(file.jsonObject, parentNode, context); 215 } 216 })}; 217 218 // ********************************************************************************************* // 219 220 function JSONTreePlate() 221 { 222 // Used by Firebug.DOMPanel.DirTable domplate. 223 this.toggles = new ToggleBranch.ToggleBranch(); 224 } 225 226 // xxxHonza: this object is *not* a panel (using Firebug terminology), but 227 // there is no other way how to subclass the DOM Tree than to derive from the DOMBasePanel. 228 // Better solution would be to have a middle object between DirTablePlate and DOMBasePanel. 229 JSONTreePlate.prototype = Obj.extend(Firebug.DOMBasePanel.prototype, 230 { 231 dispatchName: "JSONTreePlate", 232 233 render: function(jsonObject, parentNode, context) 234 { 235 try 236 { 237 this.panelNode = parentNode; 238 this.context = context; 239 240 var members = this.getMembers(jsonObject, 0, context); 241 this.expandMembers(members, this.toggles, 0, 0, context); 242 this.showMembers(members, false, false); 243 } 244 catch (err) 245 { 246 if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER) 247 FBTrace.sysout("jsonviewer.render; EXCEPTION", err); 248 } 249 }, 250 251 getMembers: function(object, level, context) 252 { 253 if (!level) 254 level = 0; 255 256 var members = []; 257 258 for (var name in object) 259 { 260 var val = object[name]; 261 this.addMember(object, "user", members, name, val, level, 0); 262 } 263 264 function sortName(a, b) { return a.name > b.name ? 1 : -1; } 265 266 // Sort only if it isn't an array (issue 4382). 267 if (Firebug.sortJsonPreview && !Arr.isArray(object, context.window)) 268 members.sort(sortName); 269 270 return members; 271 } 272 }); 273 274 // ********************************************************************************************* // 275 // Registration 276 277 Firebug.registerModule(Firebug.JSONViewerModel); 278 279 return Firebug.JSONViewerModel; 280 281 // ********************************************************************************************* // 282 }); 283