├── .gitmodules ├── .nojekyll ├── README ├── css ├── diagnosticBar.css ├── sourceView.css ├── tree.css └── ui.css ├── deploy.sh ├── download_profile.sh ├── favicon.png ├── images ├── circlearrow.svg ├── diagnostic │ ├── cache.png │ ├── cc.png │ ├── gc.png │ ├── io.png │ ├── js.png │ ├── plugin.png │ ├── snapshot.png │ └── text.png ├── filter.png ├── noise.png ├── showall.png ├── throbber.svg ├── treetwisty.png └── treetwisty.svg ├── index.html ├── js ├── ProgressReporter.js ├── QueryData.compressed.js ├── beautify.js ├── diagnosticBar.js ├── frameView.js ├── localStorage.js ├── parser.js ├── parserWorker.js ├── plugins │ └── protovis │ │ ├── index.html │ │ └── protovis-r3.2.js ├── profileCompare.js ├── sourceView.js ├── tree.js ├── ui.js ├── videoPane.js └── zip.js │ ├── deflate.js │ ├── inflate.js │ ├── zip-fs.js │ └── zip.js ├── run_webserver.sh ├── sample.big ├── sample.log ├── sample.profile └── tests ├── FrameView.dat └── README /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/.gitmodules -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/.nojekyll -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Cleopatra 2 | ========= 3 | 4 | Cleopatra is a webpage to visualize performance profiles. It was written to be used by the Gecko Profiler but can in theory be used by any profiler that can output to JSON. The UI runs entirely client-side except for a few profile storage and retrieval option. 5 | 6 | Code 7 | ==== 8 | Directory js: 9 | ui.js - Fetches profiles, dispatches heavy requests to parserWorker.js, display the processed data. 10 | parserWorker.js - Parses the profiles, handling filtering, searching and grouping. 11 | tree.js - Custom tree view control. 12 | 13 | Running 14 | ======= 15 | 1) Open index.html. Note that some features, such as reading local profiles, will either require you to run a webserver using 'run_webserver.sh' if you have python installed or setting 'security.fileuri.strict_origin_policy;false' in about:config. 16 | 2) Add ?report= to an existing profile you have upload for easy testing. 17 | 18 | or 19 | 20 | 1) Install the 'Gecko Profiler Add-on' 21 | 2) Set 'profiler.url' to your local copy of index.html such as 'file:///Volumes/Guest%20OS/Users/bgirard/ben/sps/cleopatra/index.html' and 'Analyze' a profile. 22 | 23 | or 24 | 25 | 1) Open index.html and load a profile from a file 26 | 27 | Contributing 28 | ============ 29 | 1) Fork 'https://github.com/bgirard/cleopatra' on github. 30 | 2) Push changes to your local fork. 31 | 3) Submit a github pull request 32 | -------------------------------------------------------------------------------- /css/diagnosticBar.css: -------------------------------------------------------------------------------- 1 | .diagnostic { 2 | position: relative; 3 | height: 16px; 4 | background-color: white; 5 | border-bottom: 1px solid #CCC; 6 | cursor: default; 7 | background: -moz-linear-gradient(#CCC, #EEE); 8 | background: -webkit-linear-gradient(#CCC, #EEE); 9 | background: linear-gradient(#CCC, #EEE); 10 | -moz-user-select: none; 11 | -webkit-user-select: none; 12 | user-select: none; 13 | } 14 | 15 | .diagnosticItemEven { 16 | background: -moz-linear-gradient(#900, #E00); 17 | background: -webkit-linear-gradient(#900, #E00); 18 | background: linear-gradient(#900, #E00); 19 | } 20 | 21 | .diagnosticItemOdd { 22 | background: -moz-linear-gradient(#300, #500); 23 | background: -webkit-linear-gradient(#300, #500); 24 | background: linear-gradient(#300, #500); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /css/sourceView.css: -------------------------------------------------------------------------------- 1 | .sourceViewContainer { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | bottom: 0; 6 | right: 0; 7 | z-index: 2000; 8 | background-color: white; 9 | } 10 | 11 | .sourceContainer { 12 | overflow: scroll; 13 | position: absolute; 14 | top: 30px; 15 | left: 0px; 16 | right: 0px; 17 | bottom: 0px; 18 | } 19 | 20 | .lineCountDiv { 21 | min-width: 30px; 22 | max-width: 30px; 23 | overflow-x: hidden; 24 | display: inline-block; 25 | float: left; 26 | } 27 | 28 | .lineSourceDiv { 29 | display: inline-block; 30 | } 31 | 32 | .sourceViewTrail { 33 | top: 0; 34 | right: 0; 35 | height: 29px; 36 | left: 0; 37 | background: -moz-linear-gradient(#FFF 50%, #F3F3F3 55%); 38 | border-bottom: 1px solid #CCC; 39 | margin: 0; 40 | padding: 0; 41 | overflow: hidden; 42 | } 43 | 44 | .sourceViewTrailItem, 45 | .sourceViewTrailButton { 46 | background: -moz-linear-gradient(#FFF 50%, #F3F3F3 55%); 47 | display: block; 48 | margin: 0; 49 | padding: 0; 50 | line-height: 29px; 51 | -moz-user-select: none; 52 | cursor: default; 53 | padding: 0 10px; 54 | font-size: 12px; 55 | border-right: 1px solid #CCC; 56 | text-overflow: ellipsis; 57 | white-space: nowrap; 58 | /*position: relative;*/ 59 | } 60 | 61 | .sourceViewTrailButton { 62 | float: left; 63 | } 64 | 65 | -------------------------------------------------------------------------------- /css/tree.css: -------------------------------------------------------------------------------- 1 | .treeViewContainer { 2 | -moz-user-select: none; 3 | -webkit-user-select: none; 4 | user-select: none; 5 | cursor: default; 6 | line-height: 16px; 7 | height: 100%; 8 | outline: none; /* override the browser's focus styling */ 9 | position: relative; 10 | } 11 | 12 | .treeHeader { 13 | position: absolute; 14 | top: 0; 15 | right: 0; 16 | left: 0; 17 | height: 16px; 18 | margin: 0; 19 | padding: 0; 20 | } 21 | 22 | .treeColumnHeader { 23 | position: absolute; 24 | display: block; 25 | background: -moz-linear-gradient(#FFF 45%, #EEE 60%); 26 | background: -webkit-linear-gradient(#FFF 45%, #EEE 60%); 27 | background: linear-gradient(#FFF 45%, #EEE 60%); 28 | margin: 0; 29 | padding: 0; 30 | top: 0; 31 | height: 15px; 32 | line-height: 15px; 33 | border: 0 solid #CCC; 34 | border-bottom-width: 1px; 35 | text-indent: 5px; 36 | } 37 | 38 | .treeColumnHeader:not(:last-child) { 39 | border-right-width: 1px; 40 | } 41 | 42 | .treeColumnHeader0 { 43 | left: 0; 44 | width: 86px; 45 | } 46 | 47 | .treeColumnHeader1 { 48 | left: 99px; 49 | width: 35px; 50 | } 51 | 52 | .treeColumnHeader0, 53 | .treeColumnHeader1 { 54 | text-align: right; 55 | padding-right: 12px; 56 | } 57 | 58 | .treeColumnHeader2 { 59 | left: 147px; 60 | width: 20px; 61 | } 62 | 63 | .treeColumnHeader3 { 64 | left: 168px; 65 | right: 0; 66 | } 67 | 68 | .treeViewVerticalScrollbox { 69 | position: absolute; 70 | top: 16px; 71 | left: 0; 72 | right: 0; 73 | bottom: 0; 74 | overflow-y: scroll; 75 | overflow-x: hidden; 76 | } 77 | 78 | .treeViewNode, 79 | .treeViewHorizontalScrollbox { 80 | display: block; 81 | margin: 0; 82 | padding: 0; 83 | } 84 | 85 | .treeViewNode { 86 | min-width: -moz-min-content; 87 | white-space: nowrap; 88 | } 89 | 90 | .treeViewHorizontalScrollbox { 91 | padding-left: 171px; 92 | overflow: hidden; 93 | } 94 | 95 | .treeViewVerticalScrollbox, 96 | .treeViewHorizontalScrollbox { 97 | background: -moz-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); 98 | background: -webkit-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); 99 | background: linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); 100 | background-size: 100px 32px; 101 | } 102 | 103 | .leftColumnBackground { 104 | background: -moz-linear-gradient(left, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px, transparent 146px, #CCC 146px, #CCC 147px, transparent 147px), 105 | -moz-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); 106 | background: -webkit-linear-gradient(left, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px, transparent 146px, #CCC 146px, #CCC 147px, transparent 147px), 107 | -webkit-linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); 108 | background: linear-gradient(left, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px, transparent 146px, #CCC 146px, #CCC 147px, transparent 147px), 109 | linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); 110 | background-size: auto, 100px 32px; 111 | position: absolute; 112 | top: 0; 113 | left: 0; 114 | width: 167px; 115 | min-height: 100%; 116 | border-right: 1px solid #CCC; 117 | } 118 | 119 | .sampleCount, 120 | .samplePercentage, 121 | .selfSampleCount, 122 | .resourceIcon { 123 | position: absolute; 124 | text-align: right; 125 | } 126 | 127 | .sampleCount { 128 | left: 2px; 129 | width: 50px; 130 | } 131 | 132 | .samplePercentage { 133 | left: 55px; 134 | width: 40px; 135 | } 136 | 137 | .selfSampleCount { 138 | left: 98px; 139 | width: 45px; 140 | padding-right: 2px; 141 | border: solid #CCC; 142 | border-width: 0 1px; 143 | } 144 | 145 | .resourceIcon { 146 | left: 146px; 147 | width: 18px; 148 | height: 16px; 149 | padding-right: 2px; 150 | border: solid #CCC; 151 | border-width: 0 1px; 152 | background-repeat: no-repeat; 153 | background-size: 14px 14px; 154 | background-position: 3px 1px; 155 | } 156 | 157 | .libraryName { 158 | margin-left: 10px; 159 | color: #999; 160 | } 161 | 162 | .treeViewNode > .treeViewNodeList { 163 | margin-left: 1em; 164 | } 165 | 166 | .treeViewNode.collapsed > .treeViewNodeList { 167 | display: none; 168 | } 169 | 170 | .treeLine { 171 | /* extend the selection background almost infinitely to the left */ 172 | margin-left: -10000px; 173 | padding-left: 10000px; 174 | } 175 | 176 | .treeLine.selected { 177 | color: black; 178 | background-color: -moz-dialog; 179 | } 180 | 181 | .treeLine.selected > .sampleCount { 182 | background-color: inherit; 183 | margin-left: -2px; 184 | padding-left: 2px; 185 | padding-right: 115px; 186 | margin-right: -115px; 187 | } 188 | 189 | .treeViewContainer:focus .treeLine.selected { 190 | color: highlighttext; 191 | background-color: highlight; 192 | } 193 | 194 | .treeViewContainer:focus .treeLine.selected > .libraryName { 195 | color: #CCC; 196 | } 197 | 198 | .expandCollapseButton, 199 | .focusCallstackButton { 200 | background: none 0 0 no-repeat transparent; 201 | margin: 0; 202 | padding: 0; 203 | border: 0; 204 | width: 16px; 205 | height: 16px; 206 | overflow: hidden; 207 | vertical-align: top; 208 | color: transparent; 209 | font-size: 0; 210 | } 211 | 212 | .expandCollapseButton { 213 | background-image: url(../images/treetwisty.png); 214 | } 215 | 216 | .focusCallstackButton { 217 | background-image: url(../images/circlearrow.svg); 218 | margin-left: 5px; 219 | visibility: hidden; 220 | } 221 | 222 | .expandCollapseButton:active:hover, 223 | .focusCallstackButton:active:hover { 224 | background-position: -16px 0; 225 | } 226 | 227 | .treeViewNode.collapsed > .treeLine > .expandCollapseButton { 228 | background-position: 0 -16px; 229 | } 230 | 231 | .treeViewNode.collapsed > .treeLine > .expandCollapseButton:active:hover { 232 | background-position: -16px -16px; 233 | } 234 | 235 | .treeViewContainer:focus .treeLine.selected > .expandCollapseButton, 236 | .treeViewContainer:focus .treeLine.selected > .focusCallstackButton { 237 | background-position: -32px 0; 238 | } 239 | 240 | .treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton { 241 | background-position: -32px -16px; 242 | } 243 | 244 | .treeViewContainer:focus .treeLine.selected > .expandCollapseButton:active:hover, 245 | .treeViewContainer:focus .treeLine.selected > .focusCallstackButton:active:hover { 246 | background-position: -48px 0; 247 | } 248 | 249 | .treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton:active:hover { 250 | background-position: -48px -16px; 251 | } 252 | 253 | .treeViewNode.leaf > * > .expandCollapseButton { 254 | visibility: hidden; 255 | } 256 | 257 | .treeLine:hover > .focusCallstackButton { 258 | visibility: visible; 259 | } 260 | -------------------------------------------------------------------------------- /css/ui.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: "Lucida Grande", sans-serif; 4 | font-size: 11px; 5 | } 6 | #mainarea { 7 | position: absolute; 8 | top: 0; 9 | left: 200px; 10 | bottom: 0; 11 | right: 0; 12 | } 13 | .finishedProfilePane, 14 | .finishedProfilePaneBackgroundCover, 15 | .profileEntryPane, 16 | .profileProgressPane { 17 | position: absolute; 18 | top: 0; 19 | left: 0; 20 | bottom: 0; 21 | right: 0; 22 | } 23 | .profileEntryPane { 24 | overflow: auto; 25 | } 26 | .profileEntryPane, 27 | .profileProgressPane { 28 | padding: 20px; 29 | background-color: rgb(229,229,229); 30 | background-image: url(../images/noise.png), 31 | -moz-linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2)); 32 | background-image: url(../images/noise.png), 33 | -webkit-linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2)); 34 | background-image: url(../images/noise.png), 35 | linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2)); 36 | text-shadow: rgba(255, 255, 255, 0.4) 0 1px; 37 | } 38 | .profileEntryPane h1 { 39 | margin-top: 0; 40 | font-size: 13px; 41 | font-weight: normal; 42 | } 43 | .profileEntryPane input[type="file"] { 44 | margin-bottom: 1em; 45 | } 46 | .profileProgressPane a { 47 | position: absolute; 48 | top: 35%; 49 | left: 30%; 50 | width: 40%; 51 | height: 16px; 52 | } 53 | .profileProgressPane progress { 54 | position: absolute; 55 | top: 40%; 56 | left: 30%; 57 | width: 40%; 58 | height: 16px; 59 | } 60 | .finishedProfilePaneBackgroundCover { 61 | -webkit-animation: darken 300ms cubic-bezier(0, 0, 1, 0); 62 | -moz-animation: darken 300ms cubic-bezier(0, 0, 1, 0); 63 | background-color: rgba(0, 0, 0, 0.5); 64 | } 65 | .finishedProfilePane { 66 | -webkit-animation: appear 300ms ease-out; 67 | -moz-animation: appear 300ms ease-out; 68 | } 69 | @-webkit-keyframes darken { 70 | from { 71 | opacity: 0; 72 | } 73 | to { 74 | opacity: 1; 75 | } 76 | } 77 | @-webkit-keyframes appear { 78 | from { 79 | -webkit-transform: scale(0.3); 80 | opacity: 0; 81 | pointer-events: none; 82 | } 83 | to { 84 | -webkit-transform: scale(1); 85 | opacity: 1; 86 | pointer-events: auto; 87 | } 88 | } 89 | @-moz-keyframes darken { 90 | from { 91 | opacity: 0; 92 | } 93 | to { 94 | opacity: 1; 95 | } 96 | } 97 | @-moz-keyframes appear { 98 | from { 99 | -moz-transform: scale(0.3); 100 | opacity: 0; 101 | pointer-events: none; 102 | } 103 | to { 104 | -moz-transform: scale(1); 105 | opacity: 1; 106 | pointer-events: auto; 107 | } 108 | } 109 | .breadcrumbTrail { 110 | top: 0; 111 | right: 0; 112 | height: 29px; 113 | left: 0; 114 | background: -moz-linear-gradient(#FFF 50%, #F3F3F3 55%); 115 | background: -webkit-linear-gradient(#FFF 50%, #F3F3F3 55%); 116 | background: linear-gradient(#FFF 50%, #F3F3F3 55%); 117 | border-bottom: 1px solid #CCC; 118 | margin: 0; 119 | padding: 0; 120 | overflow: hidden; 121 | } 122 | .breadcrumbTrailItem { 123 | background: -moz-linear-gradient(#FFF 50%, #F3F3F3 55%); 124 | background: -webkit-linear-gradient(#FFF 50%, #F3F3F3 55%); 125 | background: linear-gradient(#FFF 50%, #F3F3F3 55%); 126 | display: block; 127 | margin: 0; 128 | padding: 0; 129 | float: left; 130 | line-height: 29px; 131 | padding: 0 10px; 132 | font-size: 12px; 133 | -moz-user-select: none; 134 | -webkit-user-select: none; 135 | user-select: none; 136 | cursor: default; 137 | border-right: 1px solid #CCC; 138 | max-width: 250px; 139 | overflow: hidden; 140 | text-overflow: ellipsis; 141 | white-space: nowrap; 142 | position: relative; 143 | } 144 | @-webkit-keyframes slide-out { 145 | from { 146 | margin-left: -270px; 147 | opacity: 0; 148 | } 149 | to { 150 | margin-left: 0; 151 | opacity: 1; 152 | } 153 | } 154 | @-moz-keyframes slide-out { 155 | from { 156 | margin-left: -270px; 157 | opacity: 0; 158 | } 159 | to { 160 | margin-left: 0; 161 | opacity: 1; 162 | } 163 | } 164 | .breadcrumbTrailItem:not(:first-child) { 165 | -moz-animation: slide-out; 166 | -moz-animation-duration: 400ms; 167 | -moz-animation-timing-function: ease-out; 168 | -webkit-animation: slide-out; 169 | -webkit-animation-duration: 400ms; 170 | -webkit-animation-timing-function: ease-out; 171 | } 172 | .breadcrumbTrailItem.selected { 173 | background: linear-gradient(#E5E5E5 50%, #DADADA 55%); 174 | } 175 | .breadcrumbTrailItem:not(.selected):active:hover { 176 | background: linear-gradient(#F2F2F2 50%, #E6E6E6 55%); 177 | } 178 | .breadcrumbTrailItem.deleted { 179 | -moz-transition: 400ms ease-out; 180 | -moz-transition-property: opacity, margin-left; 181 | -webkit-transition: 400ms ease-out; 182 | -webkit-transition-property: opacity, margin-left; 183 | opacity: 0; 184 | margin-left: -270px; 185 | } 186 | .treeContainer { 187 | /*For asbolute position child*/ 188 | position: relative; 189 | } 190 | .tree { 191 | height: 100%; 192 | padding: 0px 200px 0px 0px; 193 | } 194 | #sampleBar { 195 | position: absolute; 196 | float: right; 197 | left: auto; 198 | top: 0; 199 | right: 0; 200 | height: 100%; 201 | border-top: none; 202 | border-right: none; 203 | border-bottom:none; 204 | padding-top:0; 205 | } 206 | #sampleBar a { 207 | text-decoration: none; 208 | } 209 | #sampleBar a:hover { 210 | text-decoration: underline; 211 | } 212 | #fileList { 213 | position: absolute; 214 | top: 0; 215 | left: 0; 216 | bottom: 480px; /* must match .sideBar height */ 217 | width: 199px; 218 | overflow: auto; 219 | padding: 0; 220 | margin: 0; 221 | background: #DBDFE7; 222 | border-right: 1px solid #BBB; 223 | } 224 | #infoBar { 225 | border-left: none; 226 | border-bottom: none; 227 | } 228 | #infoBar dl { 229 | margin: 0; 230 | } 231 | #infoBar dt, 232 | #infoBar dd { 233 | display: inline; 234 | } 235 | #infoBar dt { 236 | font-weight: bold; 237 | } 238 | #infoBar dt::after { 239 | content: " "; 240 | white-space: pre; 241 | } 242 | #infoBar dd { 243 | margin-left: 0; 244 | } 245 | #infoBar dd::after { 246 | content: "\a"; 247 | white-space:pre; 248 | } 249 | .sideBar { 250 | -moz-box-sizing: border-box; 251 | -webkit-box-sizing: border-box; 252 | box-sizing: border-box; 253 | position: absolute; 254 | left: 0; 255 | bottom: 0; 256 | width: 200px; 257 | height: 480px; 258 | overflow: auto; 259 | padding: 3px; 260 | padding-top: 0; 261 | background: #EEE; 262 | border: 1px solid #BBB; 263 | } 264 | .sideBar h2 { 265 | font-size: 1em; 266 | padding: 1px 3px; 267 | margin: 3px -3px; 268 | background: rgba(255, 255, 255, 0.6); 269 | border: solid #CCC; 270 | border-width: 1px 0; 271 | } 272 | .sideBar h2:first-child { 273 | margin-top: 0; 274 | padding-top: 0; 275 | border-top: none; 276 | } 277 | .sideBar ul { 278 | margin: 2px 0; 279 | padding-left: 18px; 280 | } 281 | .pluginview { 282 | position: absolute; 283 | right: 0; 284 | bottom: 0; 285 | left: 0; 286 | z-index: 1; 287 | background-color: white; 288 | } 289 | .pluginviewIFrame { 290 | border-style: none; 291 | width: 100%; 292 | height: 100%; 293 | } 294 | .frameViewContainer { 295 | position: relative; 296 | height: 60px; 297 | right: 0; 298 | left: 0; 299 | border-bottom: 1px solid #CCC; 300 | background: -moz-linear-gradient(#EEE, #CCC); 301 | background: -webkit-linear-gradient(#EEE, #CCC); 302 | background: linear-gradient(#EEE, #CCC); 303 | } 304 | .histogram { 305 | position: relative; 306 | height: 60px; 307 | right: 0; 308 | left: 0; 309 | border-bottom: 1px solid #CCC; 310 | background: -moz-linear-gradient(#EEE, #CCC); 311 | background: -webkit-linear-gradient(#EEE, #CCC); 312 | background: linear-gradient(#EEE, #CCC); 313 | } 314 | .histogramHilite { 315 | position: absolute; 316 | pointer-events: none; 317 | } 318 | .histogramHilite:not(.collapsed) { 319 | background: rgba(150, 150, 150, 0.5); 320 | } 321 | .histogramMouseMarker { 322 | position: absolute; 323 | pointer-events: none; 324 | top: 0; 325 | width: 1px; 326 | height: 100%; 327 | } 328 | .histogramMouseMarker:not(.collapsed) { 329 | background: rgba(0, 0, 150, 0.7); 330 | } 331 | #iconbox { 332 | display: none; 333 | } 334 | #filter, #showall { 335 | cursor: pointer; 336 | } 337 | .markers { 338 | display: none; 339 | } 340 | .hidden { 341 | display: none !important; 342 | } 343 | .fileListItem { 344 | display: block; 345 | margin: 0; 346 | padding: 0; 347 | height: 40px; 348 | text-indent: 8px; 349 | cursor: pointer; 350 | } 351 | .fileListItem.selected { 352 | background: -moz-linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px); 353 | background: -webkit-linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px); 354 | background: linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px); 355 | color: #FFF; 356 | text-shadow: 0 1px rgba(0, 0, 0, 0.3); 357 | } 358 | .fileListItemTitle { 359 | display: block; 360 | padding-top: 6px; 361 | font-size: 12px; 362 | } 363 | .fileListItemDescription { 364 | display: block; 365 | line-height: 15px; 366 | font-size: 9px; 367 | } 368 | .busyCover { 369 | position: absolute; 370 | top: 0; 371 | right: 0; 372 | bottom: 0; 373 | left: 0; 374 | visibility: hidden; 375 | opacity: 0; 376 | pointer-events: none; 377 | background: rgba(120, 120, 120, 0.2); 378 | -moz-transition: 200ms ease-in-out; 379 | -moz-transition-property: visibility, opacity; 380 | -webkit-transition: 200ms ease-in-out; 381 | -webkit-transition-property: visibility, opacity; 382 | } 383 | .busyCover.busy { 384 | visibility: visible; 385 | opacity: 1; 386 | } 387 | .busyCover::before { 388 | content: url(../images/throbber.svg); 389 | position: absolute; 390 | top: 50%; 391 | left: 50%; 392 | margin: -12px; 393 | } 394 | label { 395 | -webkit-user-select: none; 396 | -moz-user-select: none; 397 | } 398 | .videoPane { 399 | background-color: white; 400 | width: 100%; 401 | } 402 | .video { 403 | display: block; 404 | margin-left: auto; 405 | margin-right: auto; 406 | } 407 | .profileComparatorDiv { 408 | position: absolute; 409 | width: 100%; 410 | height: 100%; 411 | } 412 | .profileComparatorSide1 { 413 | position: absolute; 414 | float: left; 415 | width: 50%; 416 | height: 100%; 417 | } 418 | .profileComparatorSide2 { 419 | position: absolute; 420 | left: 50%; 421 | width: 50%; 422 | height: 100%; 423 | } 424 | iframe { 425 | width: 100%; 426 | height: 100%; 427 | } 428 | 429 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ssh webadmin@varium.fantasytalesonline.com "cd tomcat/webapps/ROOT/cleopatra/ && git pull" 3 | ssh bgirard@people.mozilla.org "cd public_html/cleopatra && git checkout * && git pull && chmod -R 755 ." 4 | -------------------------------------------------------------------------------- /download_profile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | wget http://profile-store.commondatastorage.googleapis.com/$1 3 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/favicon.png -------------------------------------------------------------------------------- /images/circlearrow.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /images/diagnostic/cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/cache.png -------------------------------------------------------------------------------- /images/diagnostic/cc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/cc.png -------------------------------------------------------------------------------- /images/diagnostic/gc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/gc.png -------------------------------------------------------------------------------- /images/diagnostic/io.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/io.png -------------------------------------------------------------------------------- /images/diagnostic/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/js.png -------------------------------------------------------------------------------- /images/diagnostic/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/plugin.png -------------------------------------------------------------------------------- /images/diagnostic/snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/snapshot.png -------------------------------------------------------------------------------- /images/diagnostic/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/diagnostic/text.png -------------------------------------------------------------------------------- /images/filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/filter.png -------------------------------------------------------------------------------- /images/noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/noise.png -------------------------------------------------------------------------------- /images/showall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/showall.png -------------------------------------------------------------------------------- /images/throbber.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /images/treetwisty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla/cleopatra/12c2ebb5d3e4ebf4eb061fa520987fc39b59fd8b/images/treetwisty.png -------------------------------------------------------------------------------- /images/treetwisty.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cleopatra - UI for SPS 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /js/ProgressReporter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ProgressReporter 3 | * 4 | * This class is used by long-winded tasks to report progress to observers. 5 | * If a task has subtasks that want to report their own progress, these 6 | * subtasks can have their own progress reporters which are hooked up to the 7 | * parent progress reporter, resulting in a tree structure. A parent progress 8 | * reporter will calculate its progress value as a weighted sum of its 9 | * subreporters' progress values. 10 | * 11 | * A progress reporter has a state, an action, and a progress value. 12 | * 13 | * - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED. 14 | * - action is a string that describes the current task. 15 | * - progress is the progress value as a number between 0 and 1, or NaN if 16 | * indeterminate. 17 | * 18 | * A progress reporter starts out in the WAITING state. The DOING state is 19 | * entered with the begin method which also sets the action. While the task is 20 | * executing, the progress value can be updated with the setProgress method. 21 | * When a task has finished, it can call the finish method which is just a 22 | * shorthand for setProgress(1); this will set the state to FINISHED. 23 | * 24 | * Progress observers can be added with the addListener method which takes a 25 | * function callback. Whenever the progress value or state change, all 26 | * listener callbacks will be called with the progress reporter object. The 27 | * observer can get state, progress value and action by calling the getter 28 | * methods getState(), getProgress() and getAction(). 29 | * 30 | * Creating child progress reporters for subtasks can be done with the 31 | * addSubreporter(s) methods. If a progress reporter has subreporters, normal 32 | * progress report functions (setProgress and finish) can no longer be called. 33 | * Instead, the parent reporter will listen to progress changes on its 34 | * subreporters and update its state automatically, and then notify its own 35 | * listeners. 36 | * When adding a subreporter, you are expected to provide an estimated 37 | * duration for the subtask. This value will be used as a weight when 38 | * calculating the progress of the parent reporter. 39 | */ 40 | 41 | const gDebugExpectedDurations = false; 42 | 43 | function ProgressReporter() { 44 | this._observers = []; 45 | this._subreporters = []; 46 | this._subreporterExpectedDurationsSum = 0; 47 | this._progress = 0; 48 | this._state = ProgressReporter.STATE_WAITING; 49 | this._action = ""; 50 | } 51 | 52 | ProgressReporter.STATE_WAITING = 0; 53 | ProgressReporter.STATE_DOING = 1; 54 | ProgressReporter.STATE_FINISHED = 2; 55 | 56 | ProgressReporter.prototype = { 57 | getProgress: function () { 58 | return this._progress; 59 | }, 60 | getState: function () { 61 | return this._state; 62 | }, 63 | setAction: function (action) { 64 | this._action = action; 65 | this._reportProgress(); 66 | }, 67 | getAction: function () { 68 | switch (this._state) { 69 | case ProgressReporter.STATE_WAITING: 70 | return "Waiting for preceding tasks to finish..."; 71 | case ProgressReporter.STATE_DOING: 72 | return this._action; 73 | case ProgressReporter.STATE_FINISHED: 74 | return "Finished."; 75 | default: 76 | throw "Broken state"; 77 | } 78 | }, 79 | addListener: function (callback) { 80 | this._observers.push(callback); 81 | }, 82 | addSubreporter: function (expectedDuration) { 83 | this._subreporterExpectedDurationsSum += expectedDuration; 84 | var subreporter = new ProgressReporter(); 85 | var self = this; 86 | subreporter.addListener(function (progress) { 87 | self._recalculateProgressFromSubreporters(); 88 | self._recalculateStateAndActionFromSubreporters(); 89 | self._reportProgress(); 90 | }); 91 | this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter }); 92 | return subreporter; 93 | }, 94 | addSubreporters: function (expectedDurations) { 95 | var reporters = {}; 96 | for (var key in expectedDurations) { 97 | reporters[key] = this.addSubreporter(expectedDurations[key]); 98 | } 99 | return reporters; 100 | }, 101 | begin: function (action) { 102 | this._startTime = Date.now(); 103 | this._state = ProgressReporter.STATE_DOING; 104 | this._action = action; 105 | this._reportProgress(); 106 | }, 107 | setProgress: function (progress) { 108 | if (this._subreporters.length > 0) 109 | throw "Can't call setProgress on a progress reporter with subreporters"; 110 | if (progress != this._progress && 111 | (progress == 1 || 112 | (isNaN(progress) != isNaN(this._progress)) || 113 | (progress - this._progress >= 0.01))) { 114 | this._progress = progress; 115 | if (progress == 1) 116 | this._transitionToFinished(); 117 | this._reportProgress(); 118 | } 119 | }, 120 | finish: function () { 121 | this.setProgress(1); 122 | }, 123 | _recalculateProgressFromSubreporters: function () { 124 | if (this._subreporters.length == 0) 125 | throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters"; 126 | this._progress = 0; 127 | for (var i = 0; i < this._subreporters.length; i++) { 128 | var expectedDuration = this._subreporters[i].expectedDuration; 129 | var reporter = this._subreporters[i].reporter; 130 | this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum; 131 | } 132 | }, 133 | _recalculateStateAndActionFromSubreporters: function () { 134 | if (this._subreporters.length == 0) 135 | throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters"; 136 | var actions = []; 137 | var allWaiting = true; 138 | var allFinished = true; 139 | for (var i = 0; i < this._subreporters.length; i++) { 140 | var expectedDuration = this._subreporters[i].expectedDuration; 141 | var reporter = this._subreporters[i].reporter; 142 | var state = reporter.getState(); 143 | if (state != ProgressReporter.STATE_WAITING) 144 | allWaiting = false; 145 | if (state != ProgressReporter.STATE_FINISHED) 146 | allFinished = false; 147 | if (state == ProgressReporter.STATE_DOING) 148 | actions.push(reporter.getAction()); 149 | } 150 | if (allFinished) { 151 | this._transitionToFinished(); 152 | } else if (!allWaiting) { 153 | this._state = ProgressReporter.STATE_DOING; 154 | if (actions.length == 0) { 155 | this._action = "About to start next task..." 156 | } else { 157 | this._action = actions.join("\n"); 158 | } 159 | } 160 | }, 161 | _transitionToFinished: function () { 162 | this._state = ProgressReporter.STATE_FINISHED; 163 | 164 | if (gDebugExpectedDurations) { 165 | this._realDuration = Date.now() - this._startTime; 166 | if (this._subreporters.length) { 167 | for (var i = 0; i < this._subreporters.length; i++) { 168 | var expectedDuration = this._subreporters[i].expectedDuration; 169 | var reporter = this._subreporters[i].reporter; 170 | var realDuration = reporter._realDuration; 171 | dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n"); 172 | } 173 | } 174 | } 175 | }, 176 | _reportProgress: function () { 177 | for (var i = 0; i < this._observers.length; i++) { 178 | this._observers[i](this); 179 | } 180 | }, 181 | }; 182 | -------------------------------------------------------------------------------- /js/QueryData.compressed.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | QueryData.js 4 | 5 | A function to parse data from a query string 6 | 7 | Created by Stephen Morley - http://code.stephenmorley.org/ - and released under 8 | the terms of the CC0 1.0 Universal legal code: 9 | 10 | http://creativecommons.org/publicdomain/zero/1.0/legalcode 11 | 12 | */ 13 | 14 | function QueryData(_1,_2){ 15 | if(_1==undefined){ 16 | _1=location.search?location.search:""; 17 | } 18 | if(_1.charAt(0)=="?"){ 19 | _1=_1.substring(1); 20 | } 21 | if(_1.length>0){ 22 | _1=_1.replace(/\+/g," "); 23 | var _3=_1.split(/[&;]/g); 24 | for(var _4=0;_4<_3.length;_4++){ 25 | var _5=_3[_4].split("="); 26 | var _6=decodeURIComponent(_5[0]); 27 | var _7=_5.length>1?decodeURIComponent(_5[1]):""; 28 | if(_2){ 29 | if(!(_6 in this)){ 30 | this[_6]=[]; 31 | } 32 | this[_6].push(_7); 33 | }else{ 34 | this[_6]=_7; 35 | } 36 | } 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /js/beautify.js: -------------------------------------------------------------------------------- 1 | /*jslint onevar: false, plusplus: false */ 2 | /*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */ 3 | /* 4 | 5 | JS Beautifier 6 | --------------- 7 | 8 | 9 | Written by Einar Lielmanis, 10 | http://jsbeautifier.org/ 11 | 12 | Originally converted to javascript by Vital, 13 | "End braces on own line" added by Chris J. Shull, 14 | 15 | You are free to use this in any way you want, in case you find this useful or working for you. 16 | 17 | Usage: 18 | js_beautify(js_source_text); 19 | js_beautify(js_source_text, options); 20 | 21 | The options are: 22 | indent_size (default 4) - indentation size, 23 | indent_char (default space) - character to indent with, 24 | preserve_newlines (default true) - whether existing line breaks should be preserved, 25 | max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk, 26 | 27 | jslint_happy (default false) - if true, then jslint-stricter mode is enforced. 28 | 29 | jslint_happy !jslint_happy 30 | --------------------------------- 31 | function () function() 32 | 33 | brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "expand-strict" 34 | put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line. 35 | 36 | expand-strict: put brace on own line even in such cases: 37 | 38 | var a = 39 | { 40 | a: 5, 41 | b: 6 42 | } 43 | This mode may break your scripts - e.g "return { a: 1 }" will be broken into two lines, so beware. 44 | 45 | space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)", 46 | 47 | unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65" 48 | 49 | e.g 50 | 51 | js_beautify(js_source_text, { 52 | 'indent_size': 1, 53 | 'indent_char': '\t' 54 | }); 55 | 56 | 57 | */ 58 | 59 | 60 | 61 | function js_beautify(js_source_text, options) { 62 | 63 | var input, output, token_text, last_type, last_text, last_last_text, last_word, flags, flag_store, indent_string; 64 | var whitespace, wordchar, punct, parser_pos, line_starters, digits; 65 | var prefix, token_type, do_block_just_closed; 66 | var wanted_newline, just_added_newline, n_newlines; 67 | var preindent_string = ''; 68 | 69 | 70 | // Some interpreters have unexpected results with foo = baz || bar; 71 | options = options ? options : {}; 72 | 73 | var opt_brace_style; 74 | 75 | // compatibility 76 | if (options.space_after_anon_function !== undefined && options.jslint_happy === undefined) { 77 | options.jslint_happy = options.space_after_anon_function; 78 | } 79 | if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option 80 | opt_brace_style = options.braces_on_own_line ? "expand" : "collapse"; 81 | } 82 | opt_brace_style = options.brace_style ? options.brace_style : (opt_brace_style ? opt_brace_style : "collapse"); 83 | 84 | 85 | var opt_indent_size = options.indent_size ? options.indent_size : 4; 86 | var opt_indent_char = options.indent_char ? options.indent_char : ' '; 87 | var opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? true : options.preserve_newlines; 88 | var opt_max_preserve_newlines = typeof options.max_preserve_newlines === 'undefined' ? false : options.max_preserve_newlines; 89 | var opt_jslint_happy = options.jslint_happy === 'undefined' ? false : options.jslint_happy; 90 | var opt_keep_array_indentation = typeof options.keep_array_indentation === 'undefined' ? false : options.keep_array_indentation; 91 | var opt_space_before_conditional = typeof options.space_before_conditional === 'undefined' ? true : options.space_before_conditional; 92 | var opt_indent_case = typeof options.indent_case === 'undefined' ? false : options.indent_case; 93 | var opt_unescape_strings = typeof options.unescape_strings === 'undefined' ? false : options.unescape_strings; 94 | 95 | just_added_newline = false; 96 | 97 | // cache the source's length. 98 | var input_length = js_source_text.length; 99 | 100 | function trim_output(eat_newlines) { 101 | eat_newlines = typeof eat_newlines === 'undefined' ? false : eat_newlines; 102 | while (output.length && (output[output.length - 1] === ' ' 103 | || output[output.length - 1] === indent_string 104 | || output[output.length - 1] === preindent_string 105 | || (eat_newlines && (output[output.length - 1] === '\n' || output[output.length - 1] === '\r')))) { 106 | output.pop(); 107 | } 108 | } 109 | 110 | function trim(s) { 111 | return s.replace(/^\s\s*|\s\s*$/, ''); 112 | } 113 | 114 | // we could use just string.split, but 115 | // IE doesn't like returning empty strings 116 | function split_newlines(s) { 117 | //return s.split(/\x0d\x0a|\x0a/); 118 | 119 | s = s.replace(/\x0d/g, ''); 120 | var out = [], 121 | idx = s.indexOf("\n"); 122 | while (idx !== -1) { 123 | out.push(s.substring(0, idx)); 124 | s = s.substring(idx + 1); 125 | idx = s.indexOf("\n"); 126 | } 127 | if (s.length) { 128 | out.push(s); 129 | } 130 | return out; 131 | } 132 | 133 | function force_newline() { 134 | var old_keep_array_indentation = opt_keep_array_indentation; 135 | opt_keep_array_indentation = false; 136 | print_newline(); 137 | opt_keep_array_indentation = old_keep_array_indentation; 138 | } 139 | 140 | function print_newline(ignore_repeated) { 141 | 142 | flags.eat_next_space = false; 143 | if (opt_keep_array_indentation && is_array(flags.mode)) { 144 | return; 145 | } 146 | 147 | ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated; 148 | 149 | flags.if_line = false; 150 | trim_output(); 151 | 152 | if (!output.length) { 153 | return; // no newline on start of file 154 | } 155 | 156 | if (output[output.length - 1] !== "\n" || !ignore_repeated) { 157 | just_added_newline = true; 158 | output.push("\n"); 159 | } 160 | if (preindent_string) { 161 | output.push(preindent_string); 162 | } 163 | for (var i = 0; i < flags.indentation_level; i += 1) { 164 | output.push(indent_string); 165 | } 166 | if (flags.var_line && flags.var_line_reindented) { 167 | output.push(indent_string); // skip space-stuffing, if indenting with a tab 168 | } 169 | if (flags.case_body) { 170 | output.push(indent_string); 171 | } 172 | } 173 | 174 | 175 | 176 | function print_single_space() { 177 | 178 | if (last_type === 'TK_COMMENT') { 179 | return print_newline(); 180 | } 181 | if (flags.eat_next_space) { 182 | flags.eat_next_space = false; 183 | return; 184 | } 185 | var last_output = ' '; 186 | if (output.length) { 187 | last_output = output[output.length - 1]; 188 | } 189 | if (last_output !== ' ' && last_output !== '\n' && last_output !== indent_string) { // prevent occassional duplicate space 190 | output.push(' '); 191 | } 192 | } 193 | 194 | 195 | function print_token() { 196 | just_added_newline = false; 197 | flags.eat_next_space = false; 198 | output.push(token_text); 199 | } 200 | 201 | function indent() { 202 | flags.indentation_level += 1; 203 | } 204 | 205 | 206 | function remove_indent() { 207 | if (output.length && output[output.length - 1] === indent_string) { 208 | output.pop(); 209 | } 210 | } 211 | 212 | function set_mode(mode) { 213 | if (flags) { 214 | flag_store.push(flags); 215 | } 216 | flags = { 217 | previous_mode: flags ? flags.mode : 'BLOCK', 218 | mode: mode, 219 | var_line: false, 220 | var_line_tainted: false, 221 | var_line_reindented: false, 222 | in_html_comment: false, 223 | if_line: false, 224 | in_case_statement: false, // switch(..){ INSIDE HERE } 225 | in_case: false, // we're on the exact line with "case 0:" 226 | case_body: false, // the indented case-action block 227 | eat_next_space: false, 228 | indentation_baseline: -1, 229 | indentation_level: (flags ? flags.indentation_level + (flags.case_body ? 1 : 0) + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : 0), 230 | ternary_depth: 0 231 | }; 232 | } 233 | 234 | function is_array(mode) { 235 | return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]'; 236 | } 237 | 238 | function is_expression(mode) { 239 | return in_array(mode, ['[EXPRESSION]', '(EXPRESSION)', '(FOR-EXPRESSION)', '(COND-EXPRESSION)']); 240 | } 241 | 242 | function restore_mode() { 243 | do_block_just_closed = flags.mode === 'DO_BLOCK'; 244 | if (flag_store.length > 0) { 245 | var mode = flags.mode; 246 | flags = flag_store.pop(); 247 | flags.previous_mode = mode; 248 | } 249 | } 250 | 251 | function all_lines_start_with(lines, c) { 252 | for (var i = 0; i < lines.length; i++) { 253 | var line = trim(lines[i]); 254 | if (line.charAt(0) !== c) { 255 | return false; 256 | } 257 | } 258 | return true; 259 | } 260 | 261 | function is_special_word(word) { 262 | return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']); 263 | } 264 | 265 | function in_array(what, arr) { 266 | for (var i = 0; i < arr.length; i += 1) { 267 | if (arr[i] === what) { 268 | return true; 269 | } 270 | } 271 | return false; 272 | } 273 | 274 | function look_up(exclude) { 275 | var local_pos = parser_pos; 276 | var c = input.charAt(local_pos); 277 | while (in_array(c, whitespace) && c !== exclude) { 278 | local_pos++; 279 | if (local_pos >= input_length) { 280 | return 0; 281 | } 282 | c = input.charAt(local_pos); 283 | } 284 | return c; 285 | } 286 | 287 | function get_next_token() { 288 | var i; 289 | var resulting_string; 290 | 291 | n_newlines = 0; 292 | 293 | if (parser_pos >= input_length) { 294 | return ['', 'TK_EOF']; 295 | } 296 | 297 | wanted_newline = false; 298 | 299 | var c = input.charAt(parser_pos); 300 | parser_pos += 1; 301 | 302 | 303 | var keep_whitespace = opt_keep_array_indentation && is_array(flags.mode); 304 | 305 | if (keep_whitespace) { 306 | 307 | // 308 | // slight mess to allow nice preservation of array indentation and reindent that correctly 309 | // first time when we get to the arrays: 310 | // var a = [ 311 | // ....'something' 312 | // we make note of whitespace_count = 4 into flags.indentation_baseline 313 | // so we know that 4 whitespaces in original source match indent_level of reindented source 314 | // 315 | // and afterwards, when we get to 316 | // 'something, 317 | // .......'something else' 318 | // we know that this should be indented to indent_level + (7 - indentation_baseline) spaces 319 | // 320 | var whitespace_count = 0; 321 | 322 | while (in_array(c, whitespace)) { 323 | 324 | if (c === "\n") { 325 | trim_output(); 326 | output.push("\n"); 327 | just_added_newline = true; 328 | whitespace_count = 0; 329 | } else { 330 | if (c === '\t') { 331 | whitespace_count += 4; 332 | } else if (c === '\r') { 333 | // nothing 334 | } else { 335 | whitespace_count += 1; 336 | } 337 | } 338 | 339 | if (parser_pos >= input_length) { 340 | return ['', 'TK_EOF']; 341 | } 342 | 343 | c = input.charAt(parser_pos); 344 | parser_pos += 1; 345 | 346 | } 347 | if (flags.indentation_baseline === -1) { 348 | flags.indentation_baseline = whitespace_count; 349 | } 350 | 351 | if (just_added_newline) { 352 | for (i = 0; i < flags.indentation_level + 1; i += 1) { 353 | output.push(indent_string); 354 | } 355 | if (flags.indentation_baseline !== -1) { 356 | for (i = 0; i < whitespace_count - flags.indentation_baseline; i++) { 357 | output.push(' '); 358 | } 359 | } 360 | } 361 | 362 | } else { 363 | while (in_array(c, whitespace)) { 364 | 365 | if (c === "\n") { 366 | n_newlines += ((opt_max_preserve_newlines) ? (n_newlines <= opt_max_preserve_newlines) ? 1 : 0 : 1); 367 | } 368 | 369 | 370 | if (parser_pos >= input_length) { 371 | return ['', 'TK_EOF']; 372 | } 373 | 374 | c = input.charAt(parser_pos); 375 | parser_pos += 1; 376 | 377 | } 378 | 379 | if (opt_preserve_newlines) { 380 | if (n_newlines > 1) { 381 | for (i = 0; i < n_newlines; i += 1) { 382 | print_newline(i === 0); 383 | just_added_newline = true; 384 | } 385 | } 386 | } 387 | wanted_newline = n_newlines > 0; 388 | } 389 | 390 | 391 | if (in_array(c, wordchar)) { 392 | if (parser_pos < input_length) { 393 | while (in_array(input.charAt(parser_pos), wordchar)) { 394 | c += input.charAt(parser_pos); 395 | parser_pos += 1; 396 | if (parser_pos === input_length) { 397 | break; 398 | } 399 | } 400 | } 401 | 402 | // small and surprisingly unugly hack for 1E-10 representation 403 | if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) { 404 | 405 | var sign = input.charAt(parser_pos); 406 | parser_pos += 1; 407 | 408 | var t = get_next_token(); 409 | c += sign + t[0]; 410 | return [c, 'TK_WORD']; 411 | } 412 | 413 | if (c === 'in') { // hack for 'in' operator 414 | return [c, 'TK_OPERATOR']; 415 | } 416 | if (wanted_newline && last_type !== 'TK_OPERATOR' 417 | && last_type !== 'TK_EQUALS' 418 | && !flags.if_line && (opt_preserve_newlines || last_text !== 'var')) { 419 | print_newline(); 420 | } 421 | return [c, 'TK_WORD']; 422 | } 423 | 424 | if (c === '(' || c === '[') { 425 | return [c, 'TK_START_EXPR']; 426 | } 427 | 428 | if (c === ')' || c === ']') { 429 | return [c, 'TK_END_EXPR']; 430 | } 431 | 432 | if (c === '{') { 433 | return [c, 'TK_START_BLOCK']; 434 | } 435 | 436 | if (c === '}') { 437 | return [c, 'TK_END_BLOCK']; 438 | } 439 | 440 | if (c === ';') { 441 | return [c, 'TK_SEMICOLON']; 442 | } 443 | 444 | if (c === '/') { 445 | var comment = ''; 446 | // peek for comment /* ... */ 447 | var inline_comment = true; 448 | if (input.charAt(parser_pos) === '*') { 449 | parser_pos += 1; 450 | if (parser_pos < input_length) { 451 | while (parser_pos < input_length && 452 | ! (input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) { 453 | c = input.charAt(parser_pos); 454 | comment += c; 455 | if (c === "\n" || c === "\r") { 456 | inline_comment = false; 457 | } 458 | parser_pos += 1; 459 | if (parser_pos >= input_length) { 460 | break; 461 | } 462 | } 463 | } 464 | parser_pos += 2; 465 | if (inline_comment && n_newlines === 0) { 466 | return ['/*' + comment + '*/', 'TK_INLINE_COMMENT']; 467 | } else { 468 | return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT']; 469 | } 470 | } 471 | // peek for comment // ... 472 | if (input.charAt(parser_pos) === '/') { 473 | comment = c; 474 | while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') { 475 | comment += input.charAt(parser_pos); 476 | parser_pos += 1; 477 | if (parser_pos >= input_length) { 478 | break; 479 | } 480 | } 481 | if (wanted_newline) { 482 | print_newline(); 483 | } 484 | return [comment, 'TK_COMMENT']; 485 | } 486 | 487 | } 488 | 489 | if (c === "'" || // string 490 | c === '"' || // string 491 | (c === '/' && 492 | ((last_type === 'TK_WORD' && is_special_word(last_text)) || 493 | (last_text === ')' && in_array(flags.previous_mode, ['(COND-EXPRESSION)', '(FOR-EXPRESSION)'])) || 494 | (last_type === 'TK_COMMA' || last_type === 'TK_COMMENT' || last_type === 'TK_START_EXPR' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_OPERATOR' || last_type === 'TK_EQUALS' || last_type === 'TK_EOF' || last_type === 'TK_SEMICOLON')))) { // regexp 495 | var sep = c; 496 | var esc = false; 497 | var esc1 = 0; 498 | var esc2 = 0; 499 | resulting_string = c; 500 | 501 | if (parser_pos < input_length) { 502 | if (sep === '/') { 503 | // 504 | // handle regexp separately... 505 | // 506 | var in_char_class = false; 507 | while (esc || in_char_class || input.charAt(parser_pos) !== sep) { 508 | resulting_string += input.charAt(parser_pos); 509 | if (!esc) { 510 | esc = input.charAt(parser_pos) === '\\'; 511 | if (input.charAt(parser_pos) === '[') { 512 | in_char_class = true; 513 | } else if (input.charAt(parser_pos) === ']') { 514 | in_char_class = false; 515 | } 516 | } else { 517 | esc = false; 518 | } 519 | parser_pos += 1; 520 | if (parser_pos >= input_length) { 521 | // incomplete string/rexp when end-of-file reached. 522 | // bail out with what had been received so far. 523 | return [resulting_string, 'TK_STRING']; 524 | } 525 | } 526 | 527 | } else { 528 | // 529 | // and handle string also separately 530 | // 531 | while (esc || input.charAt(parser_pos) !== sep) { 532 | resulting_string += input.charAt(parser_pos); 533 | if (esc1 && esc1 >= esc2) { 534 | esc1 = parseInt(resulting_string.substr(-esc2), 16); 535 | if (esc1 && esc1 >= 0x20 && esc1 <= 0x7e) { 536 | esc1 = String.fromCharCode(esc1); 537 | resulting_string = resulting_string.substr(0, resulting_string.length - esc2 - 2) + (((esc1 === sep) || (esc1 === '\\')) ? '\\' : '') + esc1; 538 | } 539 | esc1 = 0; 540 | } 541 | if (esc1) { 542 | esc1++; 543 | } else if (!esc) { 544 | esc = input.charAt(parser_pos) === '\\'; 545 | } else { 546 | esc = false; 547 | if (opt_unescape_strings) { 548 | if (input.charAt(parser_pos) === 'x') { 549 | esc1++; 550 | esc2 = 2; 551 | } else if (input.charAt(parser_pos) === 'u') { 552 | esc1++; 553 | esc2 = 4; 554 | } 555 | } 556 | } 557 | parser_pos += 1; 558 | if (parser_pos >= input_length) { 559 | // incomplete string/rexp when end-of-file reached. 560 | // bail out with what had been received so far. 561 | return [resulting_string, 'TK_STRING']; 562 | } 563 | } 564 | } 565 | 566 | 567 | 568 | } 569 | 570 | parser_pos += 1; 571 | 572 | resulting_string += sep; 573 | 574 | if (sep === '/') { 575 | // regexps may have modifiers /regexp/MOD , so fetch those, too 576 | while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) { 577 | resulting_string += input.charAt(parser_pos); 578 | parser_pos += 1; 579 | } 580 | } 581 | return [resulting_string, 'TK_STRING']; 582 | } 583 | 584 | if (c === '#') { 585 | 586 | 587 | if (output.length === 0 && input.charAt(parser_pos) === '!') { 588 | // shebang 589 | resulting_string = c; 590 | while (parser_pos < input_length && c !== '\n') { 591 | c = input.charAt(parser_pos); 592 | resulting_string += c; 593 | parser_pos += 1; 594 | } 595 | output.push(trim(resulting_string) + '\n'); 596 | print_newline(); 597 | return get_next_token(); 598 | } 599 | 600 | 601 | 602 | // Spidermonkey-specific sharp variables for circular references 603 | // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript 604 | // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935 605 | var sharp = '#'; 606 | if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) { 607 | do { 608 | c = input.charAt(parser_pos); 609 | sharp += c; 610 | parser_pos += 1; 611 | } while (parser_pos < input_length && c !== '#' && c !== '='); 612 | if (c === '#') { 613 | // 614 | } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') { 615 | sharp += '[]'; 616 | parser_pos += 2; 617 | } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') { 618 | sharp += '{}'; 619 | parser_pos += 2; 620 | } 621 | return [sharp, 'TK_WORD']; 622 | } 623 | } 624 | 625 | if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '') { 637 | flags.in_html_comment = false; 638 | parser_pos += 2; 639 | if (wanted_newline) { 640 | print_newline(); 641 | } 642 | return ['-->', 'TK_COMMENT']; 643 | } 644 | 645 | if (in_array(c, punct)) { 646 | while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) { 647 | c += input.charAt(parser_pos); 648 | parser_pos += 1; 649 | if (parser_pos >= input_length) { 650 | break; 651 | } 652 | } 653 | 654 | if (c === ',') { 655 | return [c, 'TK_COMMA']; 656 | } else if (c === '=') { 657 | return [c, 'TK_EQUALS']; 658 | } else { 659 | return [c, 'TK_OPERATOR']; 660 | } 661 | } 662 | 663 | return [c, 'TK_UNKNOWN']; 664 | } 665 | 666 | //---------------------------------- 667 | indent_string = ''; 668 | while (opt_indent_size > 0) { 669 | indent_string += opt_indent_char; 670 | opt_indent_size -= 1; 671 | } 672 | 673 | while (js_source_text && (js_source_text.charAt(0) === ' ' || js_source_text.charAt(0) === '\t')) { 674 | preindent_string += js_source_text.charAt(0); 675 | js_source_text = js_source_text.substring(1); 676 | } 677 | input = js_source_text; 678 | 679 | last_word = ''; // last 'TK_WORD' passed 680 | last_type = 'TK_START_EXPR'; // last token type 681 | last_text = ''; // last token text 682 | last_last_text = ''; // pre-last token text 683 | output = []; 684 | 685 | do_block_just_closed = false; 686 | 687 | whitespace = "\n\r\t ".split(''); 688 | wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split(''); 689 | digits = '0123456789'.split(''); 690 | 691 | punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::'; 692 | punct += ' <%= <% %> '; // try to be a good boy and try not to break the markup language identifiers 693 | punct = punct.split(' '); 694 | 695 | // words which should always start on new line. 696 | line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(','); 697 | 698 | // states showing if we are currently in expression (i.e. "if" case) - 'EXPRESSION', or in usual block (like, procedure), 'BLOCK'. 699 | // some formatting depends on that. 700 | flag_store = []; 701 | set_mode('BLOCK'); 702 | 703 | parser_pos = 0; 704 | while (true) { 705 | var t = get_next_token(); 706 | token_text = t[0]; 707 | token_type = t[1]; 708 | if (token_type === 'TK_EOF') { 709 | break; 710 | } 711 | 712 | switch (token_type) { 713 | 714 | case 'TK_START_EXPR': 715 | 716 | if (token_text === '[') { 717 | 718 | if (last_type === 'TK_WORD' || last_text === ')') { 719 | // this is array index specifier, break immediately 720 | // a[x], fn()[x] 721 | if (in_array(last_text, line_starters)) { 722 | print_single_space(); 723 | } 724 | set_mode('(EXPRESSION)'); 725 | print_token(); 726 | break; 727 | } 728 | 729 | if (flags.mode === '[EXPRESSION]' || flags.mode === '[INDENTED-EXPRESSION]') { 730 | if (last_last_text === ']' && last_text === ',') { 731 | // ], [ goes to new line 732 | if (flags.mode === '[EXPRESSION]') { 733 | flags.mode = '[INDENTED-EXPRESSION]'; 734 | if (!opt_keep_array_indentation) { 735 | indent(); 736 | } 737 | } 738 | set_mode('[EXPRESSION]'); 739 | if (!opt_keep_array_indentation) { 740 | print_newline(); 741 | } 742 | } else if (last_text === '[') { 743 | if (flags.mode === '[EXPRESSION]') { 744 | flags.mode = '[INDENTED-EXPRESSION]'; 745 | if (!opt_keep_array_indentation) { 746 | indent(); 747 | } 748 | } 749 | set_mode('[EXPRESSION]'); 750 | 751 | if (!opt_keep_array_indentation) { 752 | print_newline(); 753 | } 754 | } else { 755 | set_mode('[EXPRESSION]'); 756 | } 757 | } else { 758 | set_mode('[EXPRESSION]'); 759 | } 760 | 761 | 762 | 763 | } else { 764 | if (last_word === 'for') { 765 | set_mode('(FOR-EXPRESSION)'); 766 | } else if (in_array(last_word, ['if', 'while'])) { 767 | set_mode('(COND-EXPRESSION)'); 768 | } else { 769 | set_mode('(EXPRESSION)'); 770 | } 771 | } 772 | 773 | if (last_text === ';' || last_type === 'TK_START_BLOCK') { 774 | print_newline(); 775 | } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || last_text === '.') { 776 | if (wanted_newline) { 777 | print_newline(); 778 | } 779 | // do nothing on (( and )( and ][ and ]( and .( 780 | } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') { 781 | print_single_space(); 782 | } else if (last_word === 'function' || last_word === 'typeof') { 783 | // function() vs function () 784 | if (opt_jslint_happy) { 785 | print_single_space(); 786 | } 787 | } else if (in_array(last_text, line_starters) || last_text === 'catch') { 788 | if (opt_space_before_conditional) { 789 | print_single_space(); 790 | } 791 | } 792 | print_token(); 793 | 794 | break; 795 | 796 | case 'TK_END_EXPR': 797 | if (token_text === ']') { 798 | if (opt_keep_array_indentation) { 799 | if (last_text === '}') { 800 | // trim_output(); 801 | // print_newline(true); 802 | remove_indent(); 803 | print_token(); 804 | restore_mode(); 805 | break; 806 | } 807 | } else { 808 | if (flags.mode === '[INDENTED-EXPRESSION]') { 809 | if (last_text === ']') { 810 | restore_mode(); 811 | print_newline(); 812 | print_token(); 813 | break; 814 | } 815 | } 816 | } 817 | } 818 | restore_mode(); 819 | print_token(); 820 | break; 821 | 822 | case 'TK_START_BLOCK': 823 | 824 | if (last_word === 'do') { 825 | set_mode('DO_BLOCK'); 826 | } else { 827 | set_mode('BLOCK'); 828 | } 829 | if (opt_brace_style === "expand" || opt_brace_style === "expand-strict") { 830 | var empty_braces = false; 831 | if (opt_brace_style === "expand-strict") { 832 | empty_braces = (look_up() === '}'); 833 | if (!empty_braces) { 834 | print_newline(true); 835 | } 836 | } else { 837 | if (last_type !== 'TK_OPERATOR') { 838 | if (last_text === '=' || (is_special_word(last_text) && last_text !== 'else')) { 839 | print_single_space(); 840 | } else { 841 | print_newline(true); 842 | } 843 | } 844 | } 845 | print_token(); 846 | if (!empty_braces) { 847 | indent(); 848 | } 849 | } else { 850 | if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') { 851 | if (last_type === 'TK_START_BLOCK') { 852 | print_newline(); 853 | } else { 854 | print_single_space(); 855 | } 856 | } else { 857 | // if TK_OPERATOR or TK_START_EXPR 858 | if (is_array(flags.previous_mode) && last_text === ',') { 859 | if (last_last_text === '}') { 860 | // }, { in array context 861 | print_single_space(); 862 | } else { 863 | print_newline(); // [a, b, c, { 864 | } 865 | } 866 | } 867 | indent(); 868 | print_token(); 869 | } 870 | 871 | break; 872 | 873 | case 'TK_END_BLOCK': 874 | restore_mode(); 875 | if (opt_brace_style === "expand" || opt_brace_style === "expand-strict") { 876 | if (last_text !== '{') { 877 | print_newline(); 878 | } 879 | print_token(); 880 | } else { 881 | if (last_type === 'TK_START_BLOCK') { 882 | // nothing 883 | if (just_added_newline) { 884 | remove_indent(); 885 | } else { 886 | // {} 887 | trim_output(); 888 | } 889 | } else { 890 | if (is_array(flags.mode) && opt_keep_array_indentation) { 891 | // we REALLY need a newline here, but newliner would skip that 892 | opt_keep_array_indentation = false; 893 | print_newline(); 894 | opt_keep_array_indentation = true; 895 | 896 | } else { 897 | print_newline(); 898 | } 899 | } 900 | print_token(); 901 | } 902 | break; 903 | 904 | case 'TK_WORD': 905 | 906 | // no, it's not you. even I have problems understanding how this works 907 | // and what does what. 908 | if (do_block_just_closed) { 909 | // do {} ## while () 910 | print_single_space(); 911 | print_token(); 912 | print_single_space(); 913 | do_block_just_closed = false; 914 | break; 915 | } 916 | 917 | prefix = 'NONE'; 918 | 919 | if (token_text === 'function') { 920 | if (flags.var_line && last_type !== 'TK_EQUALS' ) { 921 | flags.var_line_reindented = true; 922 | } 923 | if ((just_added_newline || last_text === ';') && last_text !== '{' 924 | && last_type !== 'TK_BLOCK_COMMENT' && last_type !== 'TK_COMMENT') { 925 | // make sure there is a nice clean space of at least one blank line 926 | // before a new function definition 927 | n_newlines = just_added_newline ? n_newlines : 0; 928 | if (!opt_preserve_newlines) { 929 | n_newlines = 1; 930 | } 931 | 932 | for (var i = 0; i < 2 - n_newlines; i++) { 933 | print_newline(false); 934 | } 935 | } 936 | if (last_type === 'TK_WORD') { 937 | if (last_text === 'get' || last_text === 'set' || last_text === 'new' || last_text === 'return') { 938 | print_single_space(); 939 | } else { 940 | print_newline(); 941 | } 942 | } else if (last_type === 'TK_OPERATOR' || last_text === '=') { 943 | // foo = function 944 | print_single_space(); 945 | } else if (is_expression(flags.mode)) { 946 | //ää print nothing 947 | } else { 948 | print_newline(); 949 | } 950 | 951 | print_token(); 952 | last_word = token_text; 953 | break; 954 | } 955 | 956 | if (token_text === 'case' || (token_text === 'default' && flags.in_case_statement)) { 957 | if (last_text === ':' || flags.case_body) { 958 | // switch cases following one another 959 | remove_indent(); 960 | } else { 961 | // case statement starts in the same line where switch 962 | if (!opt_indent_case) { 963 | flags.indentation_level--; 964 | } 965 | print_newline(); 966 | if (!opt_indent_case) { 967 | flags.indentation_level++; 968 | } 969 | } 970 | print_token(); 971 | flags.in_case = true; 972 | flags.in_case_statement = true; 973 | flags.case_body = false; 974 | break; 975 | } 976 | 977 | if (last_type === 'TK_END_BLOCK') { 978 | 979 | if (!in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) { 980 | prefix = 'NEWLINE'; 981 | } else { 982 | if (opt_brace_style === "expand" || opt_brace_style === "end-expand" || opt_brace_style === "expand-strict") { 983 | prefix = 'NEWLINE'; 984 | } else { 985 | prefix = 'SPACE'; 986 | print_single_space(); 987 | } 988 | } 989 | } else if (last_type === 'TK_SEMICOLON' && (flags.mode === 'BLOCK' || flags.mode === 'DO_BLOCK')) { 990 | prefix = 'NEWLINE'; 991 | } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) { 992 | prefix = 'SPACE'; 993 | } else if (last_type === 'TK_STRING') { 994 | prefix = 'NEWLINE'; 995 | } else if (last_type === 'TK_WORD') { 996 | if (last_text === 'else') { 997 | // eat newlines between ...else *** some_op... 998 | // won't preserve extra newlines in this place (if any), but don't care that much 999 | trim_output(true); 1000 | } 1001 | prefix = 'SPACE'; 1002 | } else if (last_type === 'TK_START_BLOCK') { 1003 | prefix = 'NEWLINE'; 1004 | } else if (last_type === 'TK_END_EXPR') { 1005 | print_single_space(); 1006 | prefix = 'NEWLINE'; 1007 | } 1008 | 1009 | if (in_array(token_text, line_starters) && last_text !== ')') { 1010 | if (last_text === 'else') { 1011 | prefix = 'SPACE'; 1012 | } else { 1013 | prefix = 'NEWLINE'; 1014 | } 1015 | 1016 | } 1017 | 1018 | if (flags.if_line && last_type === 'TK_END_EXPR') { 1019 | flags.if_line = false; 1020 | } 1021 | if (in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) { 1022 | if (last_type !== 'TK_END_BLOCK' || opt_brace_style === "expand" || opt_brace_style === "end-expand" || opt_brace_style === "expand-strict") { 1023 | print_newline(); 1024 | } else { 1025 | trim_output(true); 1026 | print_single_space(); 1027 | } 1028 | } else if (prefix === 'NEWLINE') { 1029 | if (is_special_word(last_text)) { 1030 | // no newline between 'return nnn' 1031 | print_single_space(); 1032 | } else if (last_type !== 'TK_END_EXPR') { 1033 | if ((last_type !== 'TK_START_EXPR' || token_text !== 'var') && last_text !== ':') { 1034 | // no need to force newline on 'var': for (var x = 0...) 1035 | if (token_text === 'if' && last_word === 'else' && last_text !== '{') { 1036 | // no newline for } else if { 1037 | print_single_space(); 1038 | } else { 1039 | flags.var_line = false; 1040 | flags.var_line_reindented = false; 1041 | print_newline(); 1042 | } 1043 | } 1044 | } else if (in_array(token_text, line_starters) && last_text !== ')') { 1045 | flags.var_line = false; 1046 | flags.var_line_reindented = false; 1047 | print_newline(); 1048 | } 1049 | } else if (is_array(flags.mode) && last_text === ',' && last_last_text === '}') { 1050 | print_newline(); // }, in lists get a newline treatment 1051 | } else if (prefix === 'SPACE') { 1052 | print_single_space(); 1053 | } 1054 | print_token(); 1055 | last_word = token_text; 1056 | 1057 | if (token_text === 'var') { 1058 | flags.var_line = true; 1059 | flags.var_line_reindented = false; 1060 | flags.var_line_tainted = false; 1061 | } 1062 | 1063 | if (token_text === 'if') { 1064 | flags.if_line = true; 1065 | } 1066 | if (token_text === 'else') { 1067 | flags.if_line = false; 1068 | } 1069 | 1070 | break; 1071 | 1072 | case 'TK_SEMICOLON': 1073 | 1074 | print_token(); 1075 | flags.var_line = false; 1076 | flags.var_line_reindented = false; 1077 | if (flags.mode === 'OBJECT') { 1078 | // OBJECT mode is weird and doesn't get reset too well. 1079 | flags.mode = 'BLOCK'; 1080 | } 1081 | break; 1082 | 1083 | case 'TK_STRING': 1084 | 1085 | if (last_type === 'TK_END_EXPR' && in_array(flags.previous_mode, ['(COND-EXPRESSION)', '(FOR-EXPRESSION)'])) { 1086 | print_single_space(); 1087 | } else if (last_type === 'TK_COMMENT' || last_type === 'TK_STRING' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_SEMICOLON') { 1088 | print_newline(); 1089 | } else if (last_type === 'TK_WORD') { 1090 | print_single_space(); 1091 | } 1092 | print_token(); 1093 | break; 1094 | 1095 | case 'TK_EQUALS': 1096 | if (flags.var_line) { 1097 | // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done 1098 | flags.var_line_tainted = true; 1099 | } 1100 | print_single_space(); 1101 | print_token(); 1102 | print_single_space(); 1103 | break; 1104 | 1105 | case 'TK_COMMA': 1106 | if (flags.var_line) { 1107 | if (is_expression(flags.mode) || last_type === 'TK_END_BLOCK' ) { 1108 | // do not break on comma, for(var a = 1, b = 2) 1109 | flags.var_line_tainted = false; 1110 | } 1111 | if (flags.var_line_tainted) { 1112 | print_token(); 1113 | flags.var_line_reindented = true; 1114 | flags.var_line_tainted = false; 1115 | print_newline(); 1116 | break; 1117 | } else { 1118 | flags.var_line_tainted = false; 1119 | } 1120 | 1121 | print_token(); 1122 | print_single_space(); 1123 | break; 1124 | } 1125 | 1126 | if (last_type === 'TK_COMMENT') { 1127 | print_newline(); 1128 | } 1129 | 1130 | if (last_type === 'TK_END_BLOCK' && flags.mode !== "(EXPRESSION)") { 1131 | print_token(); 1132 | if (flags.mode === 'OBJECT' && last_text === '}') { 1133 | print_newline(); 1134 | } else { 1135 | print_single_space(); 1136 | } 1137 | } else { 1138 | if (flags.mode === 'OBJECT') { 1139 | print_token(); 1140 | print_newline(); 1141 | } else { 1142 | // EXPR or DO_BLOCK 1143 | print_token(); 1144 | print_single_space(); 1145 | } 1146 | } 1147 | break; 1148 | 1149 | 1150 | case 'TK_OPERATOR': 1151 | 1152 | var space_before = true; 1153 | var space_after = true; 1154 | 1155 | if (is_special_word(last_text)) { 1156 | // "return" had a special handling in TK_WORD. Now we need to return the favor 1157 | print_single_space(); 1158 | print_token(); 1159 | break; 1160 | } 1161 | 1162 | // hack for actionscript's import .*; 1163 | if (token_text === '*' && last_type === 'TK_UNKNOWN' && !last_last_text.match(/^\d+$/)) { 1164 | print_token(); 1165 | break; 1166 | } 1167 | 1168 | if (token_text === ':' && flags.in_case) { 1169 | if (opt_indent_case) { 1170 | flags.case_body = true; 1171 | } 1172 | print_token(); // colon really asks for separate treatment 1173 | print_newline(); 1174 | flags.in_case = false; 1175 | break; 1176 | } 1177 | 1178 | if (token_text === '::') { 1179 | // no spaces around exotic namespacing syntax operator 1180 | print_token(); 1181 | break; 1182 | } 1183 | 1184 | if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(last_text, line_starters)))) { 1185 | // unary operators (and binary +/- pretending to be unary) special cases 1186 | 1187 | space_before = false; 1188 | space_after = false; 1189 | 1190 | if (last_text === ';' && is_expression(flags.mode)) { 1191 | // for (;; ++i) 1192 | // ^^^ 1193 | space_before = true; 1194 | } 1195 | if (last_type === 'TK_WORD' && in_array(last_text, line_starters)) { 1196 | space_before = true; 1197 | } 1198 | 1199 | if (flags.mode === 'BLOCK' && (last_text === '{' || last_text === ';')) { 1200 | // { foo; --i } 1201 | // foo(); --bar; 1202 | print_newline(); 1203 | } 1204 | } else if (token_text === '.') { 1205 | // decimal digits or object.property 1206 | space_before = false; 1207 | 1208 | } else if (token_text === ':') { 1209 | if (flags.ternary_depth === 0) { 1210 | if (flags.mode === 'BLOCK') { 1211 | flags.mode = 'OBJECT'; 1212 | } 1213 | space_before = false; 1214 | } else { 1215 | flags.ternary_depth -= 1; 1216 | } 1217 | } else if (token_text === '?') { 1218 | flags.ternary_depth += 1; 1219 | } 1220 | if (space_before) { 1221 | print_single_space(); 1222 | } 1223 | 1224 | print_token(); 1225 | 1226 | if (space_after) { 1227 | print_single_space(); 1228 | } 1229 | 1230 | break; 1231 | 1232 | case 'TK_BLOCK_COMMENT': 1233 | 1234 | var lines = split_newlines(token_text); 1235 | var j; // iterator for this case 1236 | 1237 | if (all_lines_start_with(lines.slice(1), '*')) { 1238 | // javadoc: reformat and reindent 1239 | print_newline(); 1240 | output.push(lines[0]); 1241 | for (j = 1; j < lines.length; j++) { 1242 | print_newline(); 1243 | output.push(' '); 1244 | output.push(trim(lines[j])); 1245 | } 1246 | 1247 | } else { 1248 | 1249 | // simple block comment: leave intact 1250 | if (lines.length > 1) { 1251 | // multiline comment block starts with a new line 1252 | print_newline(); 1253 | } else { 1254 | // single-line /* comment */ stays where it is 1255 | if (last_type === 'TK_END_BLOCK') { 1256 | print_newline(); 1257 | } else { 1258 | print_single_space(); 1259 | } 1260 | 1261 | } 1262 | 1263 | for (j = 0; j < lines.length; j++) { 1264 | output.push(lines[j]); 1265 | output.push("\n"); 1266 | } 1267 | 1268 | } 1269 | if (look_up('\n') !== '\n') { 1270 | print_newline(); 1271 | } 1272 | break; 1273 | 1274 | case 'TK_INLINE_COMMENT': 1275 | print_single_space(); 1276 | print_token(); 1277 | if (is_expression(flags.mode)) { 1278 | print_single_space(); 1279 | } else { 1280 | force_newline(); 1281 | } 1282 | break; 1283 | 1284 | case 'TK_COMMENT': 1285 | 1286 | if (last_text === ',' && !wanted_newline) { 1287 | trim_output(true); 1288 | } 1289 | if (last_type !== 'TK_COMMENT') { 1290 | if (wanted_newline) { 1291 | print_newline(); 1292 | } else { 1293 | print_single_space(); 1294 | } 1295 | } 1296 | print_token(); 1297 | print_newline(); 1298 | break; 1299 | 1300 | case 'TK_UNKNOWN': 1301 | if (is_special_word(last_text)) { 1302 | print_single_space(); 1303 | } 1304 | print_token(); 1305 | break; 1306 | } 1307 | 1308 | last_last_text = last_text; 1309 | last_type = token_type; 1310 | last_text = token_text; 1311 | } 1312 | 1313 | var sweet_code = preindent_string + output.join('').replace(/[\r\n ]+$/, ''); 1314 | return sweet_code; 1315 | 1316 | } 1317 | 1318 | // Add support for CommonJS. Just put this file somewhere on your require.paths 1319 | // and you will be able to `var js_beautify = require("beautify").js_beautify`. 1320 | if (typeof exports !== "undefined") { 1321 | exports.js_beautify = js_beautify; 1322 | } 1323 | -------------------------------------------------------------------------------- /js/diagnosticBar.js: -------------------------------------------------------------------------------- 1 | function DiagnosticBar() { 2 | this._container = document.createElement("div"); 3 | this._container.className = "diagnostic"; 4 | this._colorCode = 0; 5 | } 6 | 7 | DiagnosticBar.prototype = { 8 | getContainer: function DiagnosticBar_getContainer() { 9 | return this._container; 10 | }, 11 | setDetailsListener: function(callback) { 12 | this._detailsListener = callback; 13 | }, 14 | _addDiagnosticItem: function(x, width, imageFile, title, details, onclickDetails) { 15 | var self = this; 16 | x = x * 100; 17 | width = width * 100; 18 | if (width < 0.1) 19 | width = 0.1; 20 | 21 | var diagnosticGradient = document.createElement("a"); 22 | if (this._colorCode % 2 == 0) { 23 | diagnosticGradient.className = "diagnosticItemEven"; 24 | } else { 25 | diagnosticGradient.className = "diagnosticItemOdd"; 26 | } 27 | 28 | var diagnostic = document.createElement("a"); 29 | 30 | var backgroundImageStr = "url('images/diagnostic/"+imageFile+"')"; 31 | diagnostic.style.position = "absolute"; 32 | diagnostic.style.backgroundImage = backgroundImageStr; 33 | diagnostic.style.backgroundRepeat = "no-repeat"; 34 | diagnostic.style.backgroundPosition = "center"; 35 | diagnostic.style.width = "100%"; 36 | diagnostic.style.height = "100%"; 37 | diagnostic.title = title + (details?"\n"+details:""); 38 | 39 | diagnosticGradient.style.position = "absolute"; 40 | diagnosticGradient.style.width = width + "%"; 41 | diagnosticGradient.style.height = "100%"; 42 | diagnosticGradient.style.backgroundRepeat = "no-repeat"; 43 | diagnosticGradient.style.backgroundPosition = "center"; 44 | diagnosticGradient.style.left = x + "%"; 45 | 46 | 47 | if (onclickDetails) { 48 | diagnostic.onclick = function() { 49 | if (self._detailsListener) { 50 | self._detailsListener(onclickDetails); 51 | } 52 | }; 53 | } 54 | diagnosticGradient.appendChild(diagnostic); 55 | this._container.appendChild(diagnosticGradient); 56 | 57 | this._colorCode++; 58 | 59 | return true; 60 | }, 61 | display: function DiagnosticBar_display(diagnosticItems) { 62 | var self = this; 63 | this._container.innerHTML = ""; 64 | 65 | var addedAnyDiagnosticItem = diagnosticItems.map(function addOneItem(item) { 66 | return self._addDiagnosticItem(item.x, item.width, item.imageFile, item.title, item.details, item.onclickDetails); 67 | }).some(function (didAdd) { return didAdd; }); 68 | 69 | if (!addedAnyDiagnosticItem) { 70 | this._container.style.display = "none"; 71 | } else { 72 | this._container.style.display = ""; 73 | } 74 | //this._container.innerHTML = w; //JSON.stringify(histogramData); 75 | }, 76 | }; 77 | 78 | 79 | -------------------------------------------------------------------------------- /js/frameView.js: -------------------------------------------------------------------------------- 1 | function FrameView() { 2 | this._container = document.createElement("div"); 3 | this._container.className = "frameViewContainer"; 4 | 5 | this._busyCover = document.createElement("div"); 6 | this._busyCover.className = "busyCover"; 7 | this._container.appendChild(this._busyCover); 8 | 9 | this._svg = this._createSvg(); 10 | this.display(); 11 | document.body.innerHTML =""; 12 | document.body.appendChild(this._svg); 13 | document.blah = sdf; 14 | } 15 | 16 | FrameView.prototype = { 17 | getContainer: function SourceView_getContainer() { 18 | return this._container; 19 | }, 20 | _createSvg: function SourceView__createSvg() { 21 | var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 22 | svg.setAttribute("version", "2.0"); 23 | return svg; 24 | }, 25 | dataIsOutdated: function HistogramView_dataIsOutdated() { 26 | this._busyCover.classList.add("busy"); 27 | }, 28 | display: function SourceView_display(histogramData, frameStart, widthSum, highlightedCallstack) { 29 | frameStart = [1, 16, 32, 100, 110, 115]; 30 | var path = "m "; 31 | for (var i = 0; i < frameStart.length - 1; i++) { 32 | var start = frameStart[i]; 33 | var end = frameStart[i+1]; 34 | var time = end - start; 35 | path += (i * 10) + "," + (50 - time) + " "; 36 | } 37 | var pathElem = document.createElementNS("http://www.w3.org/2000/svg", "path"); 38 | pathElem.setAttribute("d", path); 39 | pathElem.setAttribute("stroke", "black"); 40 | pathElem.setAttribute("stroke-width", "5"); 41 | this._svg.appendChild(pathElem); 42 | 43 | }, 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /js/localStorage.js: -------------------------------------------------------------------------------- 1 | var PROFILE_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000; 2 | 3 | // Simple wrapper for an abstract local storage provider (indexedDB) 4 | // to provide a key based JSON storage. 5 | function JSONStorage() { 6 | this._indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB; 7 | this._db = null; 8 | this._pendingRequests = []; 9 | if (!this._indexedDB) 10 | return; // No storage 11 | 12 | var dbRequest = this._indexedDB.open("cleopatra", 2); 13 | var self = this; 14 | dbRequest.onupgradeneeded = function(event) { 15 | PROFILERLOG("Upgrade cleopatra DB"); 16 | var db = event.target.result; 17 | var store = db.createObjectStore("profiles", {keyPath: "storage_key"}); 18 | } 19 | dbRequest.onsuccess = function(event) { 20 | PROFILERLOG("'cleopatra' database open"); 21 | self._db = dbRequest.result; 22 | for (var i = 0; i < self._pendingRequests.length; i++) { 23 | self._pendingRequests[i](); 24 | } 25 | self._pendingRequests = []; 26 | }; 27 | 28 | } 29 | JSONStorage.prototype = { 30 | setValue: function JSONStorage_setValue(key, value, callback) { 31 | if (!this._db) { 32 | var self = this; 33 | this._pendingRequests.push(function pendingSetValue() { 34 | self.setValue(key, value, callback); 35 | }); 36 | return; 37 | } 38 | this._db.transaction("profiles", "readwrite").objectStore("profiles").put( {storage_key: key, value: value} ); 39 | //PROFILERTRACE("JSONStorage['" + key + "'] set " + JSON.stringify(value)); 40 | if (callback) 41 | callback(); 42 | }, 43 | 44 | getValue: function JSONStorage_getValue(key, callback) { 45 | if (!this._db) { 46 | var self = this; 47 | this._pendingRequests.push(function pendingGetValue() { 48 | self.getValue(key, callback); 49 | }); 50 | return; 51 | } 52 | var transaction = this._db.transaction("profiles"); 53 | var request = transaction.objectStore("profiles").get(key); 54 | request.onsuccess = function(event) { 55 | if (!callback) 56 | return; 57 | //PROFILERTRACE("JSONStorage['" + key + "'] get " + JSON.stringify(request.result)); 58 | if (request.result) { 59 | callback(request.result.value); 60 | } else { 61 | callback(null); 62 | } 63 | } 64 | request.onerror = function() { 65 | PROFILERERROR("Error getting value from indexedDB"); 66 | } 67 | }, 68 | 69 | deleteValue: function JSONStorage_deleteValue(key, callback) { 70 | if (!this._db) { 71 | var self = this; 72 | this._pendingRequests.push(function pendingDeleteValue() { 73 | self.deleteValue(key, callback); 74 | }); 75 | return; 76 | } 77 | var transaction = this._db.transaction("profiles", "readwrite"); 78 | var request = transaction.objectStore("profiles").delete(key); 79 | request.onsuccess = function(event) { 80 | if (!callback) 81 | return; 82 | //PROFILERTRACE("JSONStorage['" + key + "'] get " + JSON.stringify(request.result)); 83 | if (request.result) { 84 | callback(request.result.value); 85 | } else { 86 | callback(null); 87 | } 88 | } 89 | request.onerror = function() { 90 | PROFILERERROR("Error deleting value from indexedDB"); 91 | } 92 | }, 93 | 94 | clearStorage: function JSONStorage_clearStorage(callback) { 95 | if (!this._db) { 96 | var self = this; 97 | this._pendingRequests.push(function pendingSetValue() { 98 | self.clearStorage(callback); 99 | }); 100 | return; 101 | } 102 | var transaction = this._db.transaction("profiles", "readwrite"); 103 | var request = transaction.objectStore("profiles").clear(); 104 | request.onsuccess = function() { 105 | PROFILERLOG("Cleared local profile storage"); 106 | if (callback) 107 | callback(); 108 | } 109 | }, 110 | } 111 | 112 | function ProfileLocalStorage() { 113 | this._storage = new JSONStorage(); 114 | this._profileListChangeCallback = null; 115 | } 116 | ProfileLocalStorage.prototype = { 117 | onProfileListChange: function ProfileLocalStorage_OnProfileListChange(callback) { 118 | this._profileListChangeCallback = callback; 119 | }, 120 | 121 | getProfileList: function ProfileLocalStorage_getProfileList(callback) { 122 | this._storage.getValue("profileList", function gotProfileList(profileList) { 123 | profileList = profileList || []; 124 | callback(profileList); 125 | }); 126 | }, 127 | 128 | storeLocalProfile: function ProfileLocalStorage_storeLocalProfile(profile, profileKey, callback) { 129 | var self = this; 130 | var date = new Date(); 131 | var time = date.getTime(); 132 | this.getProfileList(function got_profile(profileList) { 133 | profileKey = profileKey || "local_profile:" + time; 134 | for (var i = 0; i < profileList.length; i++) { 135 | if (profileList[i].profileKey == profileKey) { 136 | return; 137 | } 138 | } 139 | if (profileList.length >= 5) { 140 | var profileToRemove = profileList[0].profileKey; 141 | self.deleteLocalProfile(profileToRemove); 142 | profileList.shift(); 143 | } 144 | profileList.push( {profileKey: profileKey, key: profileKey, name: "Local Profile", date: date.getTime(), expire: time + PROFILE_EXPIRE_TIME, storedTime: time} ); 145 | self._storage.setValue(profileKey, profile); 146 | self._storage.setValue("profileList", profileList); 147 | if (callback) 148 | callback(); 149 | if (self._profileListChangeCallback) { 150 | self._profileListChangeCallback(profileList); 151 | } 152 | }); 153 | }, 154 | 155 | getProfile: function ProfileLocalStorage_getProfile(profileKey, callback) { 156 | this._storage.getValue(profileKey, callback); 157 | }, 158 | 159 | deleteLocalProfile: function ProfileLocalStorage_deleteLocalProfile(profileKey, callback) { 160 | this._storage.deleteValue(profileKey, callback); 161 | }, 162 | 163 | clearStorage: function ProfileLocalStorage_clearStorage(callback) { 164 | this._storage.clearStorage(callback); 165 | }, 166 | }; 167 | 168 | var gLocalStorage = new ProfileLocalStorage(); 169 | 170 | function quickTest() { 171 | gLocalStorage.getProfileList(function(profileList) { 172 | gLocalStorage.storeLocalProfile({}, function() { 173 | gLocalStorage.clearStorage(); 174 | }); 175 | }); 176 | } 177 | //quickTest(); 178 | -------------------------------------------------------------------------------- /js/parser.js: -------------------------------------------------------------------------------- 1 | Array.prototype.clone = function() { return this.slice(0); } 2 | 3 | function makeSample(frames, extraInfo, lines) { 4 | return { 5 | frames: frames, 6 | extraInfo: extraInfo, 7 | lines: lines 8 | }; 9 | } 10 | 11 | function cloneSample(sample) { 12 | return makeSample(sample.frames.clone(), sample.extraInfo, sample.lines.clone()); 13 | } 14 | 15 | function bucketsBySplittingArray(array, maxItemsPerBucket) { 16 | var buckets = []; 17 | while (buckets.length * maxItemsPerBucket < array.length) { 18 | buckets.push(array.slice(buckets.length * maxItemsPerBucket, 19 | (buckets.length + 1) * maxItemsPerBucket)); 20 | } 21 | return buckets; 22 | } 23 | 24 | var gParserWorker = new Worker("js/parserWorker.js"); 25 | gParserWorker.nextRequestID = 0; 26 | 27 | function WorkerRequest(worker) { 28 | var self = this; 29 | this._eventListeners = {}; 30 | var requestID = worker.nextRequestID++; 31 | this._requestID = requestID; 32 | this._worker = worker; 33 | this._totalReporter = new ProgressReporter(); 34 | this._totalReporter.addListener(function (reporter) { 35 | self._fireEvent("progress", reporter.getProgress(), reporter.getAction()); 36 | }) 37 | this._sendChunkReporter = this._totalReporter.addSubreporter(500); 38 | this._executeReporter = this._totalReporter.addSubreporter(3000); 39 | this._receiveChunkReporter = this._totalReporter.addSubreporter(100); 40 | this._totalReporter.begin("Processing task in worker..."); 41 | var partialResult = null; 42 | function onMessageFromWorker(msg) { 43 | pendingMessages.push(msg); 44 | scheduleMessageProcessing(); 45 | } 46 | function processMessage(msg) { 47 | var startTime = Date.now(); 48 | var data = msg.data; 49 | var readTime = Date.now() - startTime; 50 | if (readTime > 10) 51 | console.log("reading data from worker message: " + readTime + "ms"); 52 | if (data.requestID == requestID || !data.requestID) { 53 | switch(data.type) { 54 | case "error": 55 | self._sendChunkReporter.setAction("Error in worker: " + data.error); 56 | self._executeReporter.setAction("Error in worker: " + data.error); 57 | self._receiveChunkReporter.setAction("Error in worker: " + data.error); 58 | self._totalReporter.setAction("Error in worker: " + data.error); 59 | PROFILERERROR("Error in worker: " + data.error); 60 | self._fireEvent("error", data.error); 61 | break; 62 | case "progress": 63 | self._executeReporter.setProgress(data.progress); 64 | break; 65 | case "finished": 66 | self._executeReporter.finish(); 67 | self._receiveChunkReporter.begin("Receiving data from worker..."); 68 | self._receiveChunkReporter.finish(); 69 | self._fireEvent("finished", data.result); 70 | worker.removeEventListener("message", onMessageFromWorker); 71 | break; 72 | case "finishedStart": 73 | partialResult = null; 74 | self._totalReceiveChunks = data.numChunks; 75 | self._gotReceiveChunks = 0; 76 | self._executeReporter.finish(); 77 | self._receiveChunkReporter.begin("Receiving data from worker..."); 78 | break; 79 | case "finishedChunk": 80 | partialResult = partialResult ? partialResult.concat(data.chunk) : data.chunk; 81 | var chunkIndex = self._gotReceiveChunks++; 82 | self._receiveChunkReporter.setProgress((chunkIndex + 1) / self._totalReceiveChunks); 83 | break; 84 | case "finishedEnd": 85 | self._receiveChunkReporter.finish(); 86 | self._fireEvent("finished", partialResult); 87 | worker.removeEventListener("message", onMessageFromWorker); 88 | break; 89 | } 90 | // dump log if present 91 | if (data.log) { 92 | for (var line in data.log) { 93 | PROFILERLOG(line); 94 | } 95 | } 96 | } 97 | } 98 | var pendingMessages = []; 99 | var messageProcessingTimer = 0; 100 | function processMessages() { 101 | messageProcessingTimer = 0; 102 | processMessage(pendingMessages.shift()); 103 | if (pendingMessages.length) 104 | scheduleMessageProcessing(); 105 | } 106 | function scheduleMessageProcessing() { 107 | if (messageProcessingTimer) 108 | return; 109 | messageProcessingTimer = setTimeout(processMessages, 10); 110 | } 111 | worker.addEventListener("message", onMessageFromWorker); 112 | } 113 | 114 | WorkerRequest.prototype = { 115 | send: function WorkerRequest_send(task, taskData) { 116 | this._sendChunkReporter.begin("Sending data to worker..."); 117 | var startTime = Date.now(); 118 | this._worker.postMessage({ 119 | requestID: this._requestID, 120 | task: task, 121 | taskData: taskData 122 | }); 123 | var postTime = Date.now() - startTime; 124 | if (true || postTime > 10) 125 | console.log("posting message to worker: " + postTime + "ms"); 126 | this._sendChunkReporter.finish(); 127 | this._executeReporter.begin("Processing worker request..."); 128 | }, 129 | sendInChunks: function WorkerRequest_sendInChunks(task, taskData, params, maxChunkSize) { 130 | this._sendChunkReporter.begin("Sending data to worker..."); 131 | var self = this; 132 | var chunks = bucketsBySplittingArray(taskData, maxChunkSize); 133 | var pendingMessages = [ 134 | { 135 | requestID: this._requestID, 136 | task: "chunkedStart", 137 | numChunks: chunks.length 138 | } 139 | ].concat(chunks.map(function (chunk) { 140 | return { 141 | requestID: self._requestID, 142 | task: "chunkedChunk", 143 | chunk: chunk 144 | }; 145 | })).concat([ 146 | { 147 | requestID: this._requestID, 148 | task: "chunkedEnd" 149 | }, 150 | { 151 | requestID: this._requestID, 152 | params: params, 153 | task: task 154 | }, 155 | ]); 156 | var totalMessages = pendingMessages.length; 157 | var numSentMessages = 0; 158 | function postMessage(msg) { 159 | var msgIndex = numSentMessages++; 160 | var startTime = Date.now(); 161 | self._worker.postMessage(msg); 162 | var postTime = Date.now() - startTime; 163 | if (postTime > 10) 164 | console.log("posting message to worker: " + postTime + "ms"); 165 | self._sendChunkReporter.setProgress((msgIndex + 1) / totalMessages); 166 | } 167 | var messagePostingTimer = 0; 168 | function postMessages() { 169 | messagePostingTimer = 0; 170 | postMessage(pendingMessages.shift()); 171 | if (pendingMessages.length) { 172 | scheduleMessagePosting(); 173 | } else { 174 | self._sendChunkReporter.finish(); 175 | self._executeReporter.begin("Processing worker request..."); 176 | } 177 | } 178 | function scheduleMessagePosting() { 179 | if (messagePostingTimer) 180 | return; 181 | messagePostingTimer = setTimeout(postMessages, 10); 182 | } 183 | scheduleMessagePosting(); 184 | }, 185 | 186 | // TODO: share code with TreeView 187 | addEventListener: function WorkerRequest_addEventListener(eventName, callbackFunction) { 188 | if (!(eventName in this._eventListeners)) 189 | this._eventListeners[eventName] = []; 190 | if (this._eventListeners[eventName].indexOf(callbackFunction) != -1) 191 | return; 192 | this._eventListeners[eventName].push(callbackFunction); 193 | }, 194 | removeEventListener: function WorkerRequest_removeEventListener(eventName, callbackFunction) { 195 | if (!(eventName in this._eventListeners)) 196 | return; 197 | var index = this._eventListeners[eventName].indexOf(callbackFunction); 198 | if (index == -1) 199 | return; 200 | this._eventListeners[eventName].splice(index, 1); 201 | }, 202 | _fireEvent: function WorkerRequest__fireEvent(eventName, eventObject, p1) { 203 | if (!(eventName in this._eventListeners)) 204 | return; 205 | this._eventListeners[eventName].forEach(function (callbackFunction) { 206 | callbackFunction(eventObject, p1); 207 | }); 208 | }, 209 | } 210 | 211 | var Parser = { 212 | parse: function Parser_parse(data, params) { 213 | console.log("profile num chars: " + data.length); 214 | var request = new WorkerRequest(gParserWorker); 215 | request.sendInChunks("parseRawProfile", data, params, 3000000); 216 | return request; 217 | }, 218 | 219 | updateFilters: function Parser_updateFilters(filters) { 220 | var request = new WorkerRequest(gParserWorker); 221 | request.send("updateFilters", { 222 | filters: filters, 223 | profileID: 0 224 | }); 225 | return request; 226 | }, 227 | 228 | updateViewOptions: function Parser_updateViewOptions(options) { 229 | var request = new WorkerRequest(gParserWorker); 230 | request.send("updateViewOptions", { 231 | options: options, 232 | profileID: 0 233 | }); 234 | return request; 235 | }, 236 | 237 | getSerializedProfile: function Parser_getSerializedProfile(complete, callback) { 238 | var request = new WorkerRequest(gParserWorker); 239 | request.send("getSerializedProfile", { 240 | profileID: 0, 241 | complete: complete 242 | }); 243 | request.addEventListener("finished", callback); 244 | }, 245 | 246 | calculateHistogramData: function Parser_calculateHistogramData() { 247 | var request = new WorkerRequest(gParserWorker); 248 | request.send("calculateHistogramData", { 249 | profileID: 0 250 | }); 251 | return request; 252 | }, 253 | 254 | calculateDiagnosticItems: function Parser_calculateDiagnosticItems(meta) { 255 | var request = new WorkerRequest(gParserWorker); 256 | request.send("calculateDiagnosticItems", { 257 | profileID: 0, 258 | meta: meta 259 | }); 260 | return request; 261 | }, 262 | 263 | updateLogSetting: function Parser_updateLogSetting() { 264 | var request = new WorkerRequest(gParserWorker); 265 | request.send("initWorker", { 266 | debugLog: gDebugLog, 267 | debugTrace: gDebugTrace, 268 | }); 269 | return request; 270 | }, 271 | }; 272 | -------------------------------------------------------------------------------- /js/plugins/protovis/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dendrogram Layout 4 | 5 | 6 | 9 | 10 |
11 | 141 |
142 | 143 | -------------------------------------------------------------------------------- /js/profileCompare.js: -------------------------------------------------------------------------------- 1 | function openProfileCompare() { 2 | new ProfileComparator(document.body); 3 | } 4 | function ProfileComparator(topLevelDiv) { 5 | var self = this; 6 | 7 | this._container = document.createElement("div"); 8 | this._container.className = "profileComparatorDiv"; 9 | 10 | this._side1 = document.createElement("div"); 11 | this._side1.id = "side1"; 12 | this._side1.className = "profileComparatorSide1"; 13 | this._side2 = document.createElement("div"); 14 | this._side2.id = "side2"; 15 | this._side2.className = "profileComparatorSide2"; 16 | this._side = [this._side1, this._side2]; 17 | this._container.appendChild(this._side1); 18 | this._container.appendChild(this._side2); 19 | 20 | // Take every element in the topLevelDiv and nest it in 21 | // a profile comparator div 22 | while (topLevelDiv.firstChild) { 23 | var elemToMove = topLevelDiv.firstChild; 24 | topLevelDiv.removeChild(elemToMove); 25 | this._side1.appendChild(elemToMove); 26 | } 27 | topLevelDiv.appendChild(this._container); 28 | 29 | // create an iframe for side2 30 | this._side2iFrame = document.createElement("iframe"); 31 | this._side2iFrame.src = "file:///Volumes/Guest OS/Users/bgirard/ben/sps/cleopatra/index.html?"; 32 | this._side2iFrame.onload = function() { 33 | //self._side2iFrame.contentWindow.enterProgressUI(); 34 | } 35 | this._side2.appendChild(this._side2iFrame); 36 | this._side1.window = window; 37 | this._side2.window = self._side2iFrame.contentWindow; 38 | 39 | this._side1.window.comparator_changeFocus = function(elem) { 40 | self._changeFocus(self._side1, self._side2, elem); 41 | } 42 | this._side2.window.comparator_changeFocus = function(elem) { 43 | self._changeFocus(self._side2, self._side1, elem); 44 | } 45 | this._side1.window.comparator_setSelection = function(frames, frameData) { 46 | self._setSelection(self._side1, self._side2, frames, frameData); 47 | } 48 | this._side2.window.comparator_setSelection = function(frames, frameData) { 49 | self._setSelection(self._side2, self._side1, frames, frameData); 50 | } 51 | 52 | return this; 53 | } 54 | 55 | ProfileComparator.prototype = { 56 | getContainer: function ProfileComparator_getContainer() { 57 | return this._container; 58 | }, 59 | _isActive: function ProfileComparator__isActive(div) { 60 | var focus = document.activeElement; 61 | while (focus != null) { 62 | if (focus == div) { 63 | return true; 64 | } 65 | focus = focus.parentNode; 66 | } 67 | return false; 68 | }, 69 | _changeFocus: function ProfileComparator__changeFocus(divSrc, divDest, elem) { 70 | if (!this._isActive(divSrc)) 71 | return; 72 | 73 | elem.focus(); 74 | }, 75 | _setSelection: function ProfileComparator__setSelection(divSrc, divDest, frames, frameData) { 76 | if (!this._isActive(divSrc)) 77 | return; 78 | 79 | //dump("DIV: " + divDest.id + " has focus " + divDest.hasFocus() + "\n\n\n\n\n"); 80 | divDest.window.comparator_receiveSelection(frames, frameData); 81 | }, 82 | }; 83 | -------------------------------------------------------------------------------- /js/sourceView.js: -------------------------------------------------------------------------------- 1 | var escape = document.createElement('textarea'); 2 | 3 | function escapeHTML(html) { 4 | escape.innerHTML = html; 5 | return escape.innerHTML; 6 | } 7 | 8 | function unescapeHTML(html) { 9 | escape.innerHTML = html; 10 | return escape.value; 11 | } 12 | 13 | function replaceAll(txt, replace, with_this) { 14 | return txt.replace(new RegExp(replace, 'g'),with_this); 15 | } 16 | 17 | function formatStrSpacing(str) { 18 | str = replaceAll(str, "&", "&"); 19 | str = replaceAll(str, "<", "<"); 20 | str = replaceAll(str, ">", ">"); 21 | str = replaceAll(str, " ", " "); 22 | str = replaceAll(str, "\t", "    "); 23 | return str; 24 | } 25 | 26 | function SourceView() { 27 | this._container = document.createElement("div"); 28 | this._container.className = "sourceViewContainer"; 29 | 30 | this._buttonBar = document.createElement("div"); 31 | this._buttonBar.className = "sourceViewTrail"; 32 | 33 | this._closeButton = document.createElement("div"); 34 | this._closeButton.className = "sourceViewTrailButton"; 35 | this._closeButton.innerHTML = "[X] Close"; 36 | this._buttonBar.appendChild(this._closeButton); 37 | 38 | this._documentTitle = document.createElement("div"); 39 | this._documentTitle.className = "sourceViewTrailItem"; 40 | this._documentTitle.innerHTML = ""; 41 | this._buttonBar.appendChild(this._documentTitle); 42 | 43 | this._source = null; // String 44 | this._sourceLines = null; // String array 45 | this._sourceLinesObj = null; // String array 46 | this._sourceDiv = document.createElement("div"); 47 | this._sourceDiv.className = "sourceContainer"; 48 | 49 | var self = this; 50 | this._closeButton.onclick = function() { 51 | self._container.parentNode.removeChild(self._container); 52 | } 53 | 54 | this._container.appendChild(this._buttonBar); 55 | this._container.appendChild(this._sourceDiv); 56 | } 57 | 58 | SourceView.prototype = { 59 | getContainer: function SourceView_getContainer() { 60 | return this._container; 61 | }, 62 | 63 | setText: function SourceView_setText(title, text) { 64 | this._source = text; 65 | this._sourceLines = text.split('\n'); 66 | this._sourceLinesObj = []; 67 | this._documentTitle.textContent = title; 68 | for (var i = 0; i < this._sourceLines.length; i++) { 69 | var lineCountDiv = document.createElement("span"); 70 | lineCountDiv.innerHTML = ""; 71 | lineCountDiv.className = "lineCountDiv"; 72 | var lineTextDiv = document.createElement("span"); 73 | lineTextDiv.innerHTML = formatStrSpacing(this._sourceLines[i]); 74 | lineTextDiv.className = "lineSourceDiv"; 75 | var lineBreak = document.createElement("br"); 76 | this._sourceDiv.appendChild(lineCountDiv); 77 | this._sourceDiv.appendChild(lineTextDiv); 78 | this._sourceDiv.appendChild(lineBreak); 79 | this._sourceLinesObj.push( [lineCountDiv, lineTextDiv] ); 80 | if (false) { 81 | var scrollIntoView = lineTextDiv; 82 | setTimeout(function() { 83 | scrollIntoView.scrollIntoView(); 84 | }); 85 | lineTextDiv.style.backgroundColor = "rgba(200,0,0,0.5)"; 86 | } 87 | } 88 | 89 | }, 90 | 91 | setSource: function SourceView_setSource(source) { 92 | source = source || "Script is not available"; 93 | this._source = source; 94 | this._sourceLines = source.split('\n'); 95 | this._sourceLinesObj = []; 96 | for (var i = 0; i < this._sourceLines.length; i++) { 97 | var lineCountDiv = document.createElement("span"); 98 | lineCountDiv.innerHTML = i; 99 | lineCountDiv.className = "lineCountDiv"; 100 | var lineTextDiv = document.createElement("span"); 101 | lineTextDiv.innerHTML = formatStrSpacing(this._sourceLines[i]); 102 | lineTextDiv.className = "lineSourceDiv"; 103 | var lineBreak = document.createElement("br"); 104 | this._sourceDiv.appendChild(lineCountDiv); 105 | this._sourceDiv.appendChild(lineTextDiv); 106 | this._sourceDiv.appendChild(lineBreak); 107 | this._sourceLinesObj.push( [lineCountDiv, lineTextDiv] ); 108 | if (i == this._scriptLocation.lineInformation - 1) { 109 | var scrollIntoView = lineTextDiv; 110 | setTimeout(function() { 111 | scrollIntoView.scrollIntoView(); 112 | }); 113 | lineTextDiv.style.backgroundColor = "rgba(200,0,0,0.5)"; 114 | } 115 | } 116 | }, 117 | 118 | setScriptLocation: function SourceView_setScriptLocation(scriptLocation) { 119 | dump("View source: " + scriptLocation.lineInformation + "\n"); 120 | this._documentTitle.textContent = scriptLocation.scriptURI; 121 | this._scriptLocation = scriptLocation; 122 | }, 123 | } 124 | 125 | -------------------------------------------------------------------------------- /js/tree.js: -------------------------------------------------------------------------------- 1 | var kMaxChunkDuration = 30; // ms 2 | 3 | var escape = document.createElement('textarea'); 4 | 5 | function escapeHTML(html) { 6 | escape.innerHTML = html; 7 | return escape.innerHTML; 8 | } 9 | 10 | function unescapeHTML(html) { 11 | escape.innerHTML = html; 12 | return escape.value; 13 | } 14 | 15 | RegExp.escape = function(text) { 16 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 17 | } 18 | 19 | var requestAnimationFrame = window.webkitRequestAnimationFrame || 20 | window.mozRequestAnimationFrame || 21 | window.oRequestAnimationFrame || 22 | window.msRequestAnimationFrame || 23 | function(callback, element) { 24 | return window.setTimeout(callback, 1000 / 60); 25 | }; 26 | 27 | var cancelAnimationFrame = window.webkitCancelAnimationFrame || 28 | window.mozCancelAnimationFrame || 29 | window.oCancelAnimationFrame || 30 | window.msCancelAnimationFrame || 31 | function(req) { 32 | window.clearTimeout(req); 33 | }; 34 | 35 | function TreeView() { 36 | this._eventListeners = {}; 37 | this._pendingActions = []; 38 | this._pendingActionsProcessingCallback = null; 39 | 40 | this._container = document.createElement("div"); 41 | this._container.className = "treeViewContainer"; 42 | this._container.setAttribute("tabindex", "0"); // make it focusable 43 | 44 | this._header = document.createElement("ul"); 45 | this._header.className = "treeHeader"; 46 | this._container.appendChild(this._header); 47 | 48 | this._verticalScrollbox = document.createElement("div"); 49 | this._verticalScrollbox.className = "treeViewVerticalScrollbox"; 50 | this._container.appendChild(this._verticalScrollbox); 51 | 52 | this._leftColumnBackground = document.createElement("div"); 53 | this._leftColumnBackground.className = "leftColumnBackground"; 54 | this._verticalScrollbox.appendChild(this._leftColumnBackground); 55 | 56 | this._horizontalScrollbox = document.createElement("div"); 57 | this._horizontalScrollbox.className = "treeViewHorizontalScrollbox"; 58 | this._verticalScrollbox.appendChild(this._horizontalScrollbox); 59 | 60 | this._styleElement = document.createElement("style"); 61 | this._styleElement.setAttribute("type", "text/css"); 62 | this._container.appendChild(this._styleElement); 63 | 64 | this._contextMenu = document.createElement("menu"); 65 | this._contextMenu.setAttribute("type", "context"); 66 | this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++; 67 | this._container.appendChild(this._contextMenu); 68 | 69 | this._busyCover = document.createElement("div"); 70 | this._busyCover.className = "busyCover"; 71 | this._container.appendChild(this._busyCover); 72 | this._abortToggleAll = false; 73 | this.initSelection = true; 74 | 75 | var self = this; 76 | this._container.onkeydown = function (e) { 77 | self._onkeypress(e); 78 | }; 79 | this._container.onkeypress = function (e) { 80 | // on key down gives us '8' and mapping shift+8='*' may not be portable. 81 | if (String.fromCharCode(e.charCode) == '*') 82 | self._onkeypress(e); 83 | }; 84 | this._container.onclick = function (e) { 85 | self._onclick(e); 86 | }; 87 | this._verticalScrollbox.addEventListener("contextmenu", function(event) { 88 | self._populateContextMenu(event); 89 | }, true); 90 | this._setUpScrolling(); 91 | }; 92 | TreeView.instanceCounter = 0; 93 | 94 | TreeView.prototype = { 95 | getContainer: function TreeView_getContainer() { 96 | return this._container; 97 | }, 98 | setColumns: function TreeView_setColumns(columns) { 99 | this._header.innerHTML = ""; 100 | for (var i = 0; i < columns.length; i++) { 101 | var li = document.createElement("li"); 102 | li.className = "treeColumnHeader treeColumnHeader" + i; 103 | li.id = columns[i].name + "Header"; 104 | li.textContent = columns[i].title; 105 | this._header.appendChild(li); 106 | } 107 | }, 108 | dataIsOutdated: function TreeView_dataIsOutdated() { 109 | this._busyCover.classList.add("busy"); 110 | }, 111 | display: function TreeView_display(data, resources, filterByName) { 112 | this._busyCover.classList.remove("busy"); 113 | this._filterByName = filterByName; 114 | this._resources = resources; 115 | this._addResourceIconStyles(); 116 | this._filterByNameReg = null; // lazy init 117 | if (this._filterByName === "") 118 | this._filterByName = null; 119 | this._horizontalScrollbox.innerHTML = ""; 120 | this._horizontalScrollbox.data = data[0].getData(); 121 | if (this._pendingActionsProcessingCallback) { 122 | cancelAnimationFrame(this._pendingActionsProcessingCallback); 123 | this._pendingActionsProcessingCallback = 0; 124 | } 125 | this._pendingActions = []; 126 | 127 | this._pendingActions.push({ 128 | parentElement: this._horizontalScrollbox, 129 | parentNode: null, 130 | data: data[0].getData() 131 | }); 132 | this._processPendingActionsChunk(); 133 | if (this._initSelection === true) { 134 | this._initSelection = false; 135 | this._select(this._horizontalScrollbox.firstChild); 136 | this._toggle(this._horizontalScrollbox.firstChild); 137 | } 138 | changeFocus(this._container); 139 | }, 140 | // Provide a snapshot of the reverse selection to restore with 'invert callback' 141 | getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) { 142 | if (!this._selectedNode) 143 | return; 144 | var snapshot = []; 145 | var curr = this._selectedNode.data; 146 | 147 | while(curr) { 148 | if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) { 149 | snapshot.push(curr.name); 150 | //dump(JSON.stringify(curr.name) + "\n"); 151 | } 152 | if (curr.treeChildren && curr.treeChildren.length >= 1) { 153 | curr = curr.treeChildren[0].getData(); 154 | } else { 155 | break; 156 | } 157 | } 158 | 159 | return snapshot.reverse(); 160 | }, 161 | // Provide a snapshot of the current selection to restore 162 | getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) { 163 | var snapshot = []; 164 | var curr = this._selectedNode; 165 | 166 | while(curr) { 167 | if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) { 168 | snapshot.push(curr.data.name); 169 | //dump(JSON.stringify(curr.data.name) + "\n"); 170 | } 171 | curr = curr.treeParent; 172 | } 173 | 174 | return snapshot.reverse(); 175 | }, 176 | setSelection: function TreeView_setSelection(frames) { 177 | this.restoreSelectionSnapshot(frames, false); 178 | }, 179 | // Take a selection snapshot and restore the selection 180 | restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContigious) { 181 | //console.log("restore selection: " + JSON.stringify(snapshot)); 182 | var currNode = this._horizontalScrollbox.firstChild; 183 | if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") { 184 | snapshot.shift(); 185 | } 186 | //dump("len: " + snapshot.length + "\n"); 187 | next_level: while (currNode && snapshot.length > 0) { 188 | this._toggle(currNode, false, true); 189 | this._syncProcessPendingActionProcessing(); 190 | for (var i = 0; i < currNode.treeChildren.length; i++) { 191 | if (currNode.treeChildren[i].data.name == snapshot[0]) { 192 | //console.log("Found: " + currNode.treeChildren[i].data.name + "\n"); 193 | snapshot.shift(); 194 | this._toggle(currNode, false, true); 195 | currNode = currNode.treeChildren[i]; 196 | continue next_level; 197 | } 198 | } 199 | if (allowNonContigious === true) { 200 | // We need to do a Breadth-first search to find a match 201 | var pendingSearch = [currNode.data]; 202 | while (pendingSearch.length > 0) { 203 | var node = pendingSearch.shift(); 204 | //console.log("searching: " + node.name + " for: " + snapshot[0] + "\n"); 205 | if (!node.treeChildren) 206 | continue; 207 | for (var i = 0; i < node.treeChildren.length; i++) { 208 | var childNode = node.treeChildren[i].getData(); 209 | if (childNode.name == snapshot[0]) { 210 | //dump("found: " + childNode.name + "\n"); 211 | snapshot.shift(); 212 | var nodesToToggle = [childNode]; 213 | while (nodesToToggle[0].name != currNode.data.name) { 214 | nodesToToggle.splice(0, 0, nodesToToggle[0].parent); 215 | } 216 | var lastToggle = currNode; 217 | for (var j = 0; j < nodesToToggle.length; j++) { 218 | for (var k = 0; k < lastToggle.treeChildren.length; k++) { 219 | if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) { 220 | //dump("Expend: " + nodesToToggle[j].name + "\n"); 221 | this._toggle(lastToggle.treeChildren[k], false, true); 222 | lastToggle = lastToggle.treeChildren[k]; 223 | this._syncProcessPendingActionProcessing(); 224 | } 225 | } 226 | } 227 | currNode = lastToggle; 228 | continue next_level; 229 | } 230 | //dump("pending: " + childNode.name + "\n"); 231 | pendingSearch.push(childNode); 232 | } 233 | } 234 | } 235 | break; // Didn't find child node matching 236 | } 237 | 238 | if (currNode == this._horizontalScrollbox) { 239 | PROFILERERROR("Failed to restore selection, could not find root.\n"); 240 | return; 241 | } 242 | 243 | this._toggle(currNode, true, true); 244 | this._select(currNode); 245 | }, 246 | _processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) { 247 | this._pendingActionsProcessingCallback = 0; 248 | 249 | var startTime = Date.now(); 250 | var endTime = startTime + kMaxChunkDuration; 251 | while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) { 252 | this._processOneAction(this._pendingActions.shift()); 253 | } 254 | this._scrollHeightChanged(); 255 | 256 | this._schedulePendingActionProcessing(); 257 | }, 258 | _schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() { 259 | if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) { 260 | var self = this; 261 | this._pendingActionsProcessingCallback = requestAnimationFrame(function () { 262 | self._processPendingActionsChunk(); 263 | }); 264 | } 265 | }, 266 | _syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() { 267 | this._processPendingActionsChunk(true); 268 | }, 269 | _processOneAction: function TreeView__processOneAction(action) { 270 | var li = this._createTree(action.parentElement, action.parentNode, action.data); 271 | if ("allChildrenCollapsedValue" in action) { 272 | if (this._abortToggleAll) 273 | return; 274 | this._toggleAll(li, action.allChildrenCollapsedValue, true); 275 | } 276 | }, 277 | addEventListener: function TreeView_addEventListener(eventName, callbackFunction) { 278 | if (!(eventName in this._eventListeners)) 279 | this._eventListeners[eventName] = []; 280 | if (this._eventListeners[eventName].indexOf(callbackFunction) != -1) 281 | return; 282 | this._eventListeners[eventName].push(callbackFunction); 283 | }, 284 | removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) { 285 | if (!(eventName in this._eventListeners)) 286 | return; 287 | var index = this._eventListeners[eventName].indexOf(callbackFunction); 288 | if (index == -1) 289 | return; 290 | this._eventListeners[eventName].splice(index, 1); 291 | }, 292 | _fireEvent: function TreeView__fireEvent(eventName, eventObject) { 293 | if (!(eventName in this._eventListeners)) 294 | return; 295 | this._eventListeners[eventName].forEach(function (callbackFunction) { 296 | callbackFunction(eventObject); 297 | }); 298 | }, 299 | _setUpScrolling: function TreeView__setUpScrolling() { 300 | var waitingForPaint = false; 301 | var accumulatedDeltaX = 0; 302 | var accumulatedDeltaY = 0; 303 | var self = this; 304 | function scrollListener(e) { 305 | if (!waitingForPaint) { 306 | requestAnimationFrame(function () { 307 | self._horizontalScrollbox.scrollLeft += accumulatedDeltaX; 308 | self._verticalScrollbox.scrollTop += accumulatedDeltaY; 309 | accumulatedDeltaX = 0; 310 | accumulatedDeltaY = 0; 311 | waitingForPaint = false; 312 | }); 313 | waitingForPaint = true; 314 | } 315 | if (e.axis == e.HORIZONTAL_AXIS) { 316 | accumulatedDeltaX += e.detail; 317 | } else { 318 | accumulatedDeltaY += e.detail; 319 | } 320 | e.preventDefault(); 321 | } 322 | this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false); 323 | this._verticalScrollbox.cleanUp = function () { 324 | self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false); 325 | }; 326 | }, 327 | _scrollHeightChanged: function TreeView__scrollHeightChanged() { 328 | if (!this._pendingScrollHeightChanged) { 329 | var self = this; 330 | this._pendingScrollHeightChanged = setTimeout(function() { 331 | self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px'; 332 | self._pendingScrollHeightChanged = null; 333 | }, 0); 334 | } 335 | }, 336 | _createTree: function TreeView__createTree(parentElement, parentNode, data) { 337 | var div = document.createElement("div"); 338 | div.className = "treeViewNode collapsed"; 339 | var hasChildren = ("children" in data) && (data.children.length > 0); 340 | if (!hasChildren) 341 | div.classList.add("leaf"); 342 | var treeLine = document.createElement("div"); 343 | treeLine.className = "treeLine"; 344 | treeLine.innerHTML = this._HTMLForFunction(data); 345 | div.depth = parentNode ? parentNode.depth + 1 : 0; 346 | div.style.marginLeft = div.depth + "em"; 347 | // When this item is toggled we will expand its children 348 | div.pendingExpand = []; 349 | div.treeLine = treeLine; 350 | div.data = data; 351 | // Useful for debugging 352 | //this.uniqueID = this.uniqueID || 0; 353 | //div.id = "Node" + this.uniqueID++; 354 | div.appendChild(treeLine); 355 | div.treeChildren = []; 356 | div.treeParent = parentNode; 357 | if (hasChildren) { 358 | for (var i = 0; i < data.children.length; ++i) { 359 | div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() }); 360 | } 361 | } 362 | if (parentNode) { 363 | parentNode.treeChildren.push(div); 364 | } 365 | if (parentNode != null) { 366 | var nextTo; 367 | if (parentNode.treeChildren.length > 1) { 368 | nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling; 369 | } else { 370 | nextTo = parentNode.nextSibling; 371 | } 372 | parentElement.insertBefore(div, nextTo); 373 | } else { 374 | parentElement.appendChild(div); 375 | } 376 | return div; 377 | }, 378 | _addResourceIconStyles: function TreeView__addResourceIconStyles() { 379 | var styles = []; 380 | for (var resourceName in this._resources) { 381 | var resource = this._resources[resourceName]; 382 | if (resource.icon) { 383 | styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }'); 384 | } 385 | } 386 | this._styleElement.textContent = styles.join("\n"); 387 | }, 388 | _populateContextMenu: function TreeView__populateContextMenu(event) { 389 | this._verticalScrollbox.setAttribute("contextmenu", ""); 390 | 391 | var target = event.target; 392 | if (target.classList.contains("expandCollapseButton") || 393 | target.classList.contains("focusCallstackButton")) 394 | return; 395 | 396 | var li = this._getParentTreeViewNode(target); 397 | if (!li) 398 | return; 399 | 400 | this._select(li); 401 | 402 | this._contextMenu.innerHTML = ""; 403 | 404 | var self = this; 405 | this._contextMenuForFunction(li.data).forEach(function (menuItem) { 406 | var menuItemNode = document.createElement("menuitem"); 407 | menuItemNode.onclick = (function (menuItem) { 408 | return function() { 409 | self._contextMenuClick(li.data, menuItem); 410 | }; 411 | })(menuItem); 412 | menuItemNode.label = menuItem; 413 | self._contextMenu.appendChild(menuItemNode); 414 | }); 415 | 416 | this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id); 417 | }, 418 | _contextMenuClick: function TreeView__contextMenuClick(node, menuItem) { 419 | this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem }); 420 | }, 421 | _contextMenuForFunction: function TreeView__contextMenuForFunction(node) { 422 | // TODO move me outside tree.js 423 | var menu = []; 424 | if (node.library && ( 425 | node.library.toLowerCase() == "lib_xul" || 426 | node.library.toLowerCase() == "lib_xul.dll" 427 | )) { 428 | menu.push("View Source"); 429 | } 430 | if (node.isJSFrame && node.scriptLocation) { 431 | menu.push("View JS Source"); 432 | } 433 | menu.push("Focus Frame"); 434 | menu.push("Focus Callstack"); 435 | menu.push("Google Search"); 436 | menu.push("Plugin View: Pie"); 437 | menu.push("Plugin View: Tree"); 438 | return menu; 439 | }, 440 | _HTMLForFunction: function TreeView__HTMLForFunction(node) { 441 | var nodeName = escapeHTML(node.name); 442 | var resource = this._resources[node.library] || {}; 443 | var libName = escapeHTML(resource.name || ""); 444 | if (this._filterByName) { 445 | if (!this._filterByNameReg) { 446 | this._filterByName = RegExp.escape(this._filterByName); 447 | this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi"); 448 | } 449 | nodeName = nodeName.replace(this._filterByNameReg, "$1"); 450 | libName = libName.replace(this._filterByNameReg, "$1"); 451 | } 452 | var samplePercentage; 453 | if (isNaN(node.ratio)) { 454 | samplePercentage = ""; 455 | } else { 456 | samplePercentage = (100 * node.ratio).toFixed(1) + "%"; 457 | } 458 | return ' ' + 459 | '' + node.counter + ' ' + 460 | '' + samplePercentage + ' ' + 461 | '' + node.selfCounter + ' ' + 462 | ' ' + 463 | '' + nodeName + '' + 464 | '' + libName + '' + 465 | ''; 466 | }, 467 | _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) { 468 | while (div.pendingExpand != null && div.pendingExpand.length > 0) { 469 | var pendingExpand = div.pendingExpand.shift(); 470 | pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue; 471 | this._pendingActions.push(pendingExpand); 472 | this._schedulePendingActionProcessing(); 473 | } 474 | }, 475 | _showChild: function TreeView__showChild(div, isVisible) { 476 | for (var i = 0; i < div.treeChildren.length; i++) { 477 | div.treeChildren[i].style.display = isVisible?"":"none"; 478 | if (!isVisible) { 479 | div.treeChildren[i].classList.add("collapsed"); 480 | this._showChild(div.treeChildren[i], isVisible); 481 | } 482 | } 483 | }, 484 | _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { 485 | var currentCollapsedValue = this._isCollapsed(div); 486 | if (newCollapsedValue === undefined) 487 | newCollapsedValue = !currentCollapsedValue; 488 | if (newCollapsedValue) { 489 | div.classList.add("collapsed"); 490 | this._showChild(div, false); 491 | } else { 492 | this._resolveChildren(div, true); 493 | div.classList.remove("collapsed"); 494 | this._showChild(div, true); 495 | } 496 | if (!suppressScrollHeightNotification) 497 | this._scrollHeightChanged(); 498 | }, 499 | _toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { 500 | 501 | // Reset abort 502 | this._abortToggleAll = false; 503 | 504 | // Expands / collapses all child nodes, too. 505 | 506 | if (newCollapsedValue === undefined) 507 | newCollapsedValue = !this._isCollapsed(subtreeRoot); 508 | if (!newCollapsedValue) { 509 | // expanding 510 | this._resolveChildren(subtreeRoot, newCollapsedValue); 511 | } 512 | this._toggle(subtreeRoot, newCollapsedValue, true); 513 | for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) { 514 | this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true); 515 | } 516 | if (!suppressScrollHeightNotification) 517 | this._scrollHeightChanged(); 518 | }, 519 | _getParent: function TreeView__getParent(div) { 520 | return div.treeParent; 521 | }, 522 | _getFirstChild: function TreeView__getFirstChild(div) { 523 | if (this._isCollapsed(div)) 524 | return null; 525 | var child = div.treeChildren[0]; 526 | return child; 527 | }, 528 | _getLastChild: function TreeView__getLastChild(div) { 529 | if (this._isCollapsed(div)) 530 | return div; 531 | var lastChild = div.treeChildren[div.treeChildren.length-1]; 532 | if (lastChild == null) 533 | return div; 534 | return this._getLastChild(lastChild); 535 | }, 536 | _getPrevSib: function TreeView__getPevSib(div) { 537 | if (div.treeParent == null) 538 | return null; 539 | var nodeIndex = div.treeParent.treeChildren.indexOf(div); 540 | if (nodeIndex == 0) 541 | return null; 542 | return div.treeParent.treeChildren[nodeIndex-1]; 543 | }, 544 | _getNextSib: function TreeView__getNextSib(div) { 545 | if (div.treeParent == null) 546 | return null; 547 | var nodeIndex = div.treeParent.treeChildren.indexOf(div); 548 | if (nodeIndex == div.treeParent.treeChildren.length - 1) 549 | return this._getNextSib(div.treeParent); 550 | return div.treeParent.treeChildren[nodeIndex+1]; 551 | }, 552 | _scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) { 553 | // Schedule this on the animation frame otherwise we may run this more then once per frames 554 | // causing more work then needed. 555 | var self = this; 556 | if (self._pendingAnimationFrame != null) { 557 | return; 558 | } 559 | self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() { 560 | cancelAnimationFrame(self._pendingAnimationFrame); 561 | self._pendingAnimationFrame = null; 562 | self._scrollIntoView(element, maxImportantWidth); 563 | }); 564 | }, 565 | _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) { 566 | // Make sure that element is inside the visible part of our scrollbox by 567 | // adjusting the scroll positions. If element is wider or 568 | // higher than the scroll port, the left and top edges are prioritized over 569 | // the right and bottom edges. 570 | // If maxImportantWidth is set, parts of the beyond this widths are 571 | // considered as not important; they'll not be moved into view. 572 | 573 | if (maxImportantWidth === undefined) 574 | maxImportantWidth = Infinity; 575 | 576 | var visibleRect = { 577 | left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150 578 | top: this._verticalScrollbox.getBoundingClientRect().top, 579 | right: this._horizontalScrollbox.getBoundingClientRect().right, 580 | bottom: this._verticalScrollbox.getBoundingClientRect().bottom 581 | } 582 | var r = element.getBoundingClientRect(); 583 | var right = Math.min(r.right, r.left + maxImportantWidth); 584 | var leftCutoff = visibleRect.left - r.left; 585 | var rightCutoff = right - visibleRect.right; 586 | var topCutoff = visibleRect.top - r.top; 587 | var bottomCutoff = r.bottom - visibleRect.bottom; 588 | if (leftCutoff > 0) 589 | this._horizontalScrollbox.scrollLeft -= leftCutoff; 590 | else if (rightCutoff > 0) 591 | this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff); 592 | if (topCutoff > 0) 593 | this._verticalScrollbox.scrollTop -= topCutoff; 594 | else if (bottomCutoff > 0) 595 | this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff); 596 | }, 597 | _select: function TreeView__select(li) { 598 | if (this._selectedNode != null) { 599 | this._selectedNode.treeLine.classList.remove("selected"); 600 | this._selectedNode = null; 601 | } 602 | if (li) { 603 | li.treeLine.classList.add("selected"); 604 | this._selectedNode = li; 605 | var functionName = li.treeLine.querySelector(".functionName"); 606 | this._scheduleScrollIntoView(functionName, 400); 607 | this._fireEvent("select", li.data); 608 | } 609 | updateDocumentURL(); 610 | }, 611 | _isCollapsed: function TreeView__isCollapsed(div) { 612 | return div.classList.contains("collapsed"); 613 | }, 614 | _getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) { 615 | while (node) { 616 | if (node.nodeType != node.ELEMENT_NODE) 617 | break; 618 | if (node.classList.contains("treeViewNode")) 619 | return node; 620 | node = node.parentNode; 621 | } 622 | return null; 623 | }, 624 | _onclick: function TreeView__onclick(event) { 625 | var target = event.target; 626 | var node = this._getParentTreeViewNode(target); 627 | if (!node) 628 | return; 629 | if (target.classList.contains("expandCollapseButton")) { 630 | if (event.altKey) 631 | this._toggleAll(node); 632 | else 633 | this._toggle(node); 634 | } else if (target.classList.contains("focusCallstackButton")) { 635 | this._fireEvent("focusCallstackButtonClicked", node.data); 636 | } else { 637 | this._select(node); 638 | if (event.detail == 2) // dblclick 639 | this._toggle(node); 640 | } 641 | }, 642 | _onkeypress: function TreeView__onkeypress(event) { 643 | if (event.ctrlKey || event.altKey || event.metaKey) 644 | return; 645 | 646 | this._abortToggleAll = true; 647 | 648 | var selected = this._selectedNode; 649 | if (event.keyCode < 37 || event.keyCode > 40) { 650 | if (event.keyCode != 0 || 651 | String.fromCharCode(event.charCode) != '*') { 652 | return; 653 | } 654 | } 655 | event.stopPropagation(); 656 | event.preventDefault(); 657 | if (!selected) 658 | return; 659 | if (event.keyCode == 37) { // KEY_LEFT 660 | var isCollapsed = this._isCollapsed(selected); 661 | if (!isCollapsed) { 662 | this._toggle(selected); 663 | } else { 664 | var parent = this._getParent(selected); 665 | if (parent != null) { 666 | this._select(parent); 667 | } 668 | } 669 | } else if (event.keyCode == 38) { // KEY_UP 670 | var prevSib = this._getPrevSib(selected); 671 | var parent = this._getParent(selected); 672 | if (prevSib != null) { 673 | this._select(this._getLastChild(prevSib)); 674 | } else if (parent != null) { 675 | this._select(parent); 676 | } 677 | } else if (event.keyCode == 39) { // KEY_RIGHT 678 | var isCollapsed = this._isCollapsed(selected); 679 | if (isCollapsed) { 680 | this._toggle(selected); 681 | this._syncProcessPendingActionProcessing(); 682 | } else { 683 | // Do KEY_DOWN 684 | var nextSib = this._getNextSib(selected); 685 | var child = this._getFirstChild(selected); 686 | if (child != null) { 687 | this._select(child); 688 | } else if (nextSib) { 689 | this._select(nextSib); 690 | } 691 | } 692 | } else if (event.keyCode == 40) { // KEY_DOWN 693 | var nextSib = this._getNextSib(selected); 694 | var child = this._getFirstChild(selected); 695 | if (child != null) { 696 | this._select(child); 697 | } else if (nextSib) { 698 | this._select(nextSib); 699 | } 700 | } else if (String.fromCharCode(event.charCode) == '*') { 701 | this._toggleAll(selected); 702 | } 703 | }, 704 | }; 705 | 706 | -------------------------------------------------------------------------------- /js/videoPane.js: -------------------------------------------------------------------------------- 1 | function VideoPane(videoCapture) { 2 | this._container = document.createElement("div"); 3 | this._container.className = "videoPane"; 4 | this._onTimeChange = null; 5 | 6 | this._video = document.createElement("video"); 7 | this._video.className = "video"; 8 | //this._video.width = 480; 9 | this._video.controls = "controls"; 10 | this._video.crossOrigin = 'anonymous'; 11 | this._video.crossorigin = 'anonymous'; 12 | this._video.src = videoCapture.src; 13 | this._container.appendChild(this._video); 14 | 15 | this._canvas = document.createElement("canvas"); 16 | 17 | // When we get a time update we fire a callback because 18 | // the updated frame might not have been ready. 19 | this._timeUpdateCallback = null; 20 | } 21 | 22 | VideoPane.prototype = { 23 | getContainer: function VideoPane_getContainer() { 24 | return this._container; 25 | }, 26 | onTimeChange: function VideoPane_onTimeChange(callback) { 27 | var self = this; 28 | this._video.addEventListener("timeupdate", function() { 29 | callback(self._video); 30 | if (self._timeUpdateCallback) { 31 | clearTimeout(self._timeUpdateCallback); 32 | } 33 | self._timeUpdateCallback = setTimeout(function timeUpdateCallback() { 34 | callback(self._video); 35 | self._timeUpdateCallback = null; 36 | }, 100); 37 | }); 38 | }, 39 | getCurrentFrameNumber: function VideoPane_getCurrentFrameNumber() { 40 | if (this._canvas.width != this._video.videoWidth || 41 | this._canvas.height != this._video.videoHeight) { 42 | this._canvas.width = this._video.videoWidth; 43 | this._canvas.height = this._video.videoHeight; 44 | } 45 | 46 | var context = this._canvas.getContext("2d"); 47 | context.drawImage(this._video, 0, 0, this._canvas.width, this._canvas.height); 48 | var frame = context.getImageData(0, 0, this._canvas.width, this._canvas.height); 49 | 50 | var TOLERENCE = 50; 51 | var frameNumber = 0; 52 | for (var i = 0; i < 16; i++) { 53 | var currColor = null; 54 | // Look for each frame counter bit, a 3x3 black/white dot 55 | // sample only from the top middle pixel since it has the lowest chance of compression leakage 56 | for (var x = 1; x < 2; x++) { 57 | for (var y = 0; y < 1; y++) { 58 | var r = frame.data[(i*3 + x + y*this._canvas.width) * 4 + 0]; 59 | var g = frame.data[(i*3 + x + y*this._canvas.width) * 4 + 1]; 60 | var b = frame.data[(i*3 + x + y*this._canvas.width) * 4 + 2]; 61 | if (currColor != "white" && r < TOLERENCE && g < TOLERENCE && b < TOLERENCE) { 62 | currColor = "black"; 63 | } else if (currColor != "black" && r > 255 - TOLERENCE && g > 255 - TOLERENCE && b > 255 - TOLERENCE) { 64 | currColor = "white"; 65 | } else { 66 | //var data = this._canvas.toDataURL(); 67 | //dump("Fail to find frame: " + data + "\n"); 68 | //dump("i: " + i + "\n"); 69 | //dump("x: " + x + "\n"); 70 | //dump("y: " + y + "\n"); 71 | //dump("R: " + r + " G: " + g + " B: " + b + "\n"); 72 | //dump("Fail\n"); 73 | return null; 74 | } 75 | } 76 | } 77 | if (currColor == "black") { 78 | frameNumber += 1 << i; 79 | } 80 | } 81 | return frameNumber; 82 | } 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /js/zip.js/zip-fs.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Gildas Lormeau. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution. 13 | 14 | 3. The names of the authors may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 19 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, 20 | INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 23 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | (function(obj) { 30 | 31 | var CHUNK_SIZE = 512 * 1024; 32 | 33 | var zip = obj.zip; 34 | 35 | var FileWriter = zip.FileWriter, // 36 | TextWriter = zip.TextWriter, // 37 | BlobWriter = zip.BlobWriter, // 38 | Data64URIWriter = zip.Data64URIWriter, // 39 | Reader = zip.Reader, // 40 | TextReader = zip.TextReader, // 41 | BlobReader = zip.BlobReader, // 42 | Data64URIReader = zip.Data64URIReader, // 43 | HttpRangeReader = zip.HttpRangeReader, // 44 | HttpReader = zip.HttpReader, // 45 | createReader = zip.createReader, // 46 | createWriter = zip.createWriter; 47 | 48 | function ZipBlobReader(entry) { 49 | var that = this, blobReader; 50 | 51 | function init(callback, onerror) { 52 | this.size = entry.uncompressedSize; 53 | callback(); 54 | } 55 | 56 | function getData(callback) { 57 | if (that.data) 58 | callback(); 59 | else 60 | entry.getData(new BlobWriter(), function(data) { 61 | that.data = data; 62 | blobReader = new BlobReader(data); 63 | callback(); 64 | }, null, that.checkCrc32); 65 | } 66 | 67 | function readUint8Array(index, length, callback, onerror) { 68 | getData(function() { 69 | blobReader.readUint8Array(index, length, callback, onerror); 70 | }, onerror); 71 | } 72 | 73 | that.size = 0; 74 | that.init = init; 75 | that.readUint8Array = readUint8Array; 76 | } 77 | ZipBlobReader.prototype = new Reader(); 78 | ZipBlobReader.prototype.constructor = ZipBlobReader; 79 | ZipBlobReader.prototype.checkCrc32 = false; 80 | 81 | function getTotalSize(entry) { 82 | var size = 0; 83 | 84 | function process(entry) { 85 | size += entry.uncompressedSize || 0; 86 | entry.children.forEach(process); 87 | } 88 | 89 | process(entry); 90 | return size; 91 | } 92 | 93 | function initReaders(entry, onend, onerror) { 94 | var index = 0; 95 | 96 | function next() { 97 | var child = entry.children[index]; 98 | index++; 99 | if (index < entry.children.length) 100 | process(entry.children[index]); 101 | else 102 | onend(); 103 | } 104 | 105 | function process(child) { 106 | if (child.directory) 107 | initReaders(child, next, onerror); 108 | else { 109 | child.reader = new child.Reader(child.data, onerror); 110 | child.reader.init(function() { 111 | child.uncompressedSize = child.reader.size; 112 | next(); 113 | }); 114 | } 115 | } 116 | 117 | if (entry.children.length) 118 | process(entry.children[index]); 119 | else 120 | onend(); 121 | } 122 | 123 | function detach(entry) { 124 | var children = entry.parent.children; 125 | children.forEach(function(child, index) { 126 | if (child.id == entry.id) 127 | children.splice(index, 1); 128 | }); 129 | } 130 | 131 | function exportZip(zipWriter, entry, onend, onprogress, onerror, totalSize) { 132 | var currentIndex = 0; 133 | 134 | function process(zipWriter, entry, onend, onprogress, totalSize) { 135 | var childIndex = 0; 136 | 137 | function exportChild() { 138 | var child = entry.children[childIndex]; 139 | if (child) 140 | zipWriter.add(child.getFullname(), child.reader, function() { 141 | currentIndex += child.uncompressedSize || 0; 142 | process(zipWriter, child, function() { 143 | childIndex++; 144 | exportChild(); 145 | }, onprogress, totalSize); 146 | }, function(index) { 147 | if (onprogress) 148 | onprogress(currentIndex + index, totalSize); 149 | }, { 150 | directory : child.directory, 151 | version : child.zipVersion 152 | }); 153 | else 154 | onend(); 155 | } 156 | 157 | exportChild(); 158 | } 159 | 160 | process(zipWriter, entry, onend, onprogress, totalSize); 161 | } 162 | 163 | function addFileEntry(zipEntry, fileEntry, onend, onerror) { 164 | function getChildren(fileEntry, callback) { 165 | if (fileEntry.isDirectory) 166 | fileEntry.createReader().readEntries(callback); 167 | if (fileEntry.isFile) 168 | callback([]); 169 | } 170 | 171 | function process(zipEntry, fileEntry, onend) { 172 | getChildren(fileEntry, function(children) { 173 | var childIndex = 0; 174 | 175 | function addChild(child) { 176 | function nextChild(childFileEntry) { 177 | process(childFileEntry, child, function() { 178 | childIndex++; 179 | processChild(); 180 | }); 181 | } 182 | 183 | if (child.isDirectory) 184 | nextChild(zipEntry.addDirectory(child.name)); 185 | if (child.isFile) 186 | child.file(function(file) { 187 | var childZipEntry = zipEntry.addBlob(child.name, file); 188 | childZipEntry.uncompressedSize = file.size; 189 | nextChild(childZipEntry); 190 | }, onerror); 191 | } 192 | 193 | function processChild() { 194 | var child = children[childIndex]; 195 | if (child) 196 | addChild(child); 197 | else 198 | onend(); 199 | } 200 | 201 | processChild(); 202 | }); 203 | } 204 | 205 | if (fileEntry.isDirectory) 206 | process(zipEntry, fileEntry, onend); 207 | else 208 | fileEntry.file(function(file) { 209 | zipEntry.addBlob(fileEntry.name, file); 210 | onend(); 211 | }, onerror); 212 | } 213 | 214 | function getFileEntry(fileEntry, entry, onend, onprogress, totalSize, checkCrc32) { 215 | var currentIndex = 0, rootEntry; 216 | 217 | function process(fileEntry, entry, onend, onprogress, totalSize) { 218 | var childIndex = 0; 219 | 220 | function addChild(child) { 221 | function nextChild(childFileEntry) { 222 | currentIndex += child.uncompressedSize || 0; 223 | process(childFileEntry, child, function() { 224 | childIndex++; 225 | processChild(); 226 | }, onprogress, totalSize); 227 | } 228 | 229 | if (child.directory) 230 | fileEntry.getDirectory(child.name, { 231 | create : true 232 | }, nextChild, onerror); 233 | else 234 | fileEntry.getFile(child.name, { 235 | create : true 236 | }, function(file) { 237 | child.getData(new FileWriter(file), nextChild, function(index, max) { 238 | if (onprogress) 239 | onprogress(currentIndex + index, totalSize); 240 | }, checkCrc32); 241 | }, onerror); 242 | } 243 | 244 | function processChild() { 245 | var child = entry.children[childIndex]; 246 | if (child) 247 | addChild(child); 248 | else 249 | onend(); 250 | } 251 | 252 | processChild(); 253 | } 254 | 255 | if (entry.directory) 256 | process(fileEntry, entry, onend, onprogress, totalSize); 257 | else 258 | entry.getData(new FileWriter(fileEntry), onend, onprogress, checkCrc32); 259 | } 260 | 261 | function resetFS(fs) { 262 | fs.entries = []; 263 | fs.root = new ZipDirectoryEntry(fs); 264 | } 265 | 266 | function bufferedCopy(reader, writer, onend, onprogress, onerror) { 267 | var chunkIndex = 0; 268 | 269 | function stepCopy() { 270 | var index = chunkIndex * CHUNK_SIZE; 271 | if (onprogress) 272 | onprogress(index, reader.size); 273 | if (index < reader.size) 274 | reader.readUint8Array(index, Math.min(CHUNK_SIZE, reader.size - index), function(array) { 275 | writer.writeUint8Array(new Uint8Array(array), function() { 276 | chunkIndex++; 277 | stepCopy(); 278 | }); 279 | }, onerror); 280 | else 281 | writer.getData(onend); 282 | } 283 | 284 | stepCopy(); 285 | } 286 | 287 | function getEntryData(writer, onend, onprogress, onerror) { 288 | var that = this; 289 | if (!writer || (writer.constructor == that.Writer && that.data)) 290 | onend(that.data); 291 | else { 292 | if (!that.reader) 293 | that.reader = new that.Reader(that.data, onerror); 294 | that.reader.init(function() { 295 | writer.init(function() { 296 | bufferedCopy(that.reader, writer, onend, onprogress, onerror); 297 | }, onerror); 298 | }); 299 | } 300 | } 301 | 302 | function addChild(parent, name, params, directory) { 303 | if (parent.directory) 304 | return directory ? new ZipDirectoryEntry(parent.fs, name, params, parent) : new ZipFileEntry(parent.fs, name, params, parent); 305 | else 306 | throw "Parent entry is not a directory."; 307 | } 308 | 309 | function ZipEntry() { 310 | } 311 | 312 | ZipEntry.prototype = { 313 | init : function(fs, name, params, parent) { 314 | var that = this; 315 | if (fs.root && parent && parent.getChildByName(name)) 316 | throw "Entry filename already exists."; 317 | if (!params) 318 | params = {}; 319 | that.fs = fs; 320 | that.name = name; 321 | that.id = fs.entries.length; 322 | that.parent = parent; 323 | that.children = []; 324 | that.zipVersion = params.zipVersion || 0x14; 325 | that.uncompressedSize = 0; 326 | fs.entries.push(that); 327 | if (parent) 328 | that.parent.children.push(that); 329 | }, 330 | getFileEntry : function(fileEntry, onend, onprogress, onerror, checkCrc32) { 331 | var that = this; 332 | initReaders(that, function() { 333 | getFileEntry(fileEntry, that, onend, onprogress, getTotalSize(that), checkCrc32); 334 | }, onerror); 335 | }, 336 | moveTo : function(target) { 337 | var that = this; 338 | if (target.directory) { 339 | if (!target.isDescendantOf(that)) { 340 | if (that != target) { 341 | if (target.getChildByName(that.name)) 342 | throw "Entry filename already exists."; 343 | detach(that); 344 | that.parent = target; 345 | target.children.push(that); 346 | } 347 | } else 348 | throw "Entry is a ancestor of target entry."; 349 | } else 350 | throw "Target entry is not a directory."; 351 | }, 352 | getFullname : function() { 353 | var that = this, fullname = that.name, entry = that.parent; 354 | while (entry) { 355 | fullname = (entry.name ? entry.name + "/" : "") + fullname; 356 | entry = entry.parent; 357 | } 358 | return fullname; 359 | }, 360 | isDescendantOf : function(ancestor) { 361 | var entry = this.parent; 362 | while (entry && entry.id != ancestor.id) 363 | entry = entry.parent; 364 | return !!entry; 365 | } 366 | }; 367 | ZipEntry.prototype.constructor = ZipEntry; 368 | 369 | var ZipFileEntryProto; 370 | 371 | function ZipFileEntry(fs, name, params, parent) { 372 | var that = this; 373 | ZipEntry.prototype.init.call(that, fs, name, params, parent); 374 | that.Reader = params.Reader; 375 | that.Writer = params.Writer; 376 | that.data = params.data; 377 | that.getData = params.getData || getEntryData; 378 | } 379 | 380 | ZipFileEntry.prototype = ZipFileEntryProto = new ZipEntry(); 381 | ZipFileEntryProto.constructor = ZipFileEntry; 382 | ZipFileEntryProto.getText = function(onend, onprogress, checkCrc32) { 383 | this.getData(new TextWriter(), onend, onprogress, checkCrc32); 384 | }; 385 | ZipFileEntryProto.getBlob = function(onend, onprogress, checkCrc32) { 386 | this.getData(new BlobWriter(), onend, onprogress, checkCrc32); 387 | }; 388 | ZipFileEntryProto.getData64URI = function(mimeType, onend, onprogress, checkCrc32) { 389 | this.getData(new Data64URIWriter(mimeType), onend, onprogress, checkCrc32); 390 | }; 391 | 392 | var ZipDirectoryEntryProto; 393 | 394 | function ZipDirectoryEntry(fs, name, params, parent) { 395 | var that = this; 396 | ZipEntry.prototype.init.call(that, fs, name, params, parent); 397 | that.directory = true; 398 | } 399 | 400 | ZipDirectoryEntry.prototype = ZipDirectoryEntryProto = new ZipEntry(); 401 | ZipDirectoryEntryProto.constructor = ZipDirectoryEntry; 402 | ZipDirectoryEntryProto.addDirectory = function(name) { 403 | return addChild(this, name, null, true); 404 | }; 405 | ZipDirectoryEntryProto.addText = function(name, text) { 406 | return addChild(this, name, { 407 | data : text, 408 | Reader : TextReader, 409 | Writer : TextWriter 410 | }); 411 | }; 412 | ZipDirectoryEntryProto.addBlob = function(name, blob) { 413 | return addChild(this, name, { 414 | data : blob, 415 | Reader : BlobReader, 416 | Writer : BlobWriter 417 | }); 418 | }; 419 | ZipDirectoryEntryProto.addData64URI = function(name, dataURI) { 420 | return addChild(this, name, { 421 | data : dataURI, 422 | Reader : Data64URIReader, 423 | Writer : Data64URIWriter 424 | }); 425 | }; 426 | ZipDirectoryEntryProto.addHttpContent = function(name, URL, useRangeHeader) { 427 | return addChild(this, name, { 428 | data : URL, 429 | Reader : useRangeHeader ? HttpRangeReader : HttpReader 430 | }); 431 | }; 432 | ZipDirectoryEntryProto.addFileEntry = function(fileEntry, onend, onerror) { 433 | addFileEntry(this, fileEntry, onend, onerror); 434 | }; 435 | ZipDirectoryEntryProto.addData = function(name, params) { 436 | return addChild(this, name, params); 437 | }; 438 | ZipDirectoryEntryProto.importBlob = function(blob, onend, onerror) { 439 | this.importZip(new BlobReader(blob), onend, onerror); 440 | }; 441 | ZipDirectoryEntryProto.importText = function(text, onend, onerror) { 442 | this.importZip(new TextReader(text), onend, onerror); 443 | }; 444 | ZipDirectoryEntryProto.importData64URI = function(dataURI, onend, onerror) { 445 | this.importZip(new Data64URIReader(dataURI), onend, onerror); 446 | }; 447 | ZipDirectoryEntryProto.importHttpContent = function(URL, useRangeHeader, onend, onerror) { 448 | this.importZip(useRangeHeader ? new HttpRangeReader(URL) : new HttpReader(URL), onend, onerror); 449 | }; 450 | ZipDirectoryEntryProto.exportBlob = function(onend, onprogress, onerror) { 451 | this.exportZip(new BlobWriter(), onend, onprogress, onerror); 452 | }; 453 | ZipDirectoryEntryProto.exportText = function(onend, onprogress, onerror) { 454 | this.exportZip(new TextWriter(), onend, onprogress, onerror); 455 | }; 456 | ZipDirectoryEntryProto.exportFileEntry = function(fileEntry, onend, onprogress, onerror) { 457 | this.exportZip(new FileWriter(fileEntry), onend, onprogress, onerror); 458 | }; 459 | ZipDirectoryEntryProto.exportData64URI = function(mimeType, onend, onprogress, onerror) { 460 | this.exportZip(new Data64URIWriter(mimeType), onend, onprogress, onerror); 461 | }; 462 | ZipDirectoryEntryProto.importZip = function(reader, onend, onerror) { 463 | var that = this; 464 | createReader(reader, function(zipReader) { 465 | zipReader.getEntries(function(entries) { 466 | entries.forEach(function(entry) { 467 | var parent = that, path = entry.filename.split("/"), name = path.pop(); 468 | path.forEach(function(pathPart) { 469 | parent = parent.getChildByName(pathPart) || new ZipDirectoryEntry(that.fs, pathPart, null, parent); 470 | }); 471 | if (!entry.directory) 472 | addChild(parent, name, { 473 | data : entry, 474 | Reader : ZipBlobReader 475 | }); 476 | }); 477 | onend(); 478 | }); 479 | }, onerror); 480 | }; 481 | ZipDirectoryEntryProto.exportZip = function(writer, onend, onprogress, onerror) { 482 | var that = this; 483 | initReaders(that, function() { 484 | createWriter(writer, function(zipWriter) { 485 | exportZip(zipWriter, that, function() { 486 | zipWriter.close(onend); 487 | }, onprogress, onerror, getTotalSize(that)); 488 | }, onerror); 489 | }, onerror); 490 | }; 491 | ZipDirectoryEntryProto.getChildByName = function(name) { 492 | var childIndex, child, that = this; 493 | for (childIndex = 0; childIndex < that.children.length; childIndex++) { 494 | child = that.children[childIndex]; 495 | if (child.name == name) 496 | return child; 497 | } 498 | }; 499 | 500 | function FS() { 501 | resetFS(this); 502 | } 503 | FS.prototype = { 504 | remove : function(entry) { 505 | detach(entry); 506 | this.entries[entry.id] = null; 507 | }, 508 | find : function(fullname) { 509 | var index, path = fullname.split("/"), node = this.root; 510 | for (index = 0; node && index < path.length; index++) 511 | node = node.getChildByName(path[index]); 512 | return node; 513 | }, 514 | getById : function(id) { 515 | return this.entries[id]; 516 | }, 517 | importBlob : function(blob, onend, onerror) { 518 | resetFS(this); 519 | this.root.importBlob(blob, onend, onerror); 520 | }, 521 | importText : function(text, onend, onerror) { 522 | resetFS(this); 523 | this.root.importText(text, onend, onerror); 524 | }, 525 | importData64URI : function(dataURI, onend, onerror) { 526 | resetFS(this); 527 | this.root.importData64URI(dataURI, onend, onerror); 528 | }, 529 | importHttpContent : function(URL, useRangeHeader, onend, onerror) { 530 | resetFS(this); 531 | this.root.importHttpContent(URL, useRangeHeader, onend, onerror); 532 | }, 533 | exportBlob : function(onend, onprogress, onerror) { 534 | this.root.exportBlob(onend, onprogress, onerror); 535 | }, 536 | exportText : function(onend, onprogress, onerror) { 537 | this.root.exportText(onend, onprogress, onerror); 538 | }, 539 | exportFileEntry : function(fileEntry, onend, onprogress, onerror) { 540 | this.root.exportFileEntry(fileEntry, onend, onprogress, onerror); 541 | }, 542 | exportData64URI : function(mimeType, onend, onprogress, onerror) { 543 | this.root.exportData64URI(mimeType, onend, onprogress, onerror); 544 | } 545 | }; 546 | 547 | zip.fs = { 548 | FS : FS 549 | }; 550 | 551 | })(this); 552 | -------------------------------------------------------------------------------- /js/zip.js/zip.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012 Gildas Lormeau. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution. 13 | 14 | 3. The names of the authors may not be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, 18 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 19 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, 20 | INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 23 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | (function(obj) { 30 | 31 | var ERR_BAD_FORMAT = "File format is not recognized."; 32 | var ERR_ENCRYPTED = "File contains encrypted entry."; 33 | var ERR_ZIP64 = "File is using Zip64 (4gb+ file size)."; 34 | var ERR_READ = "Error while reading zip file."; 35 | var ERR_WRITE = "Error while writing zip file."; 36 | var ERR_WRITE_DATA = "Error while writing file data."; 37 | var ERR_READ_DATA = "Error while reading file data."; 38 | var ERR_DUPLICATED_NAME = "File already exists."; 39 | var ERR_HTTP_RANGE = "HTTP Range not supported."; 40 | var CHUNK_SIZE = 512 * 1024; 41 | 42 | var INFLATE_JS = "inflate.js"; 43 | var DEFLATE_JS = "deflate.js"; 44 | 45 | var BlobBuilder = obj.WebKitBlobBuilder || obj.MozBlobBuilder || obj.MSBlobBuilder || obj.BlobBuilder; 46 | 47 | function Crc32() { 48 | var crc = -1, that = this; 49 | that.append = function(data) { 50 | var offset, table = that.table; 51 | for (offset = 0; offset < data.length; offset++) 52 | crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF]; 53 | }; 54 | that.get = function() { 55 | return ~crc; 56 | }; 57 | } 58 | Crc32.prototype.table = (function() { 59 | var i, j, t, table = []; 60 | for (i = 0; i < 256; i++) { 61 | t = i; 62 | for (j = 0; j < 8; j++) 63 | if (t & 1) 64 | t = (t >>> 1) ^ 0xEDB88320; 65 | else 66 | t = t >>> 1; 67 | table[i] = t; 68 | } 69 | return table; 70 | })(); 71 | 72 | function blobSlice(blob, index, length) { 73 | if (blob.webkitSlice) 74 | return blob.webkitSlice(index, index + length); 75 | else if (blob.mozSlice) 76 | return blob.mozSlice(index, index + length); 77 | else if (blob.msSlice) 78 | return blob.msSlice(index, index + length); 79 | else 80 | return blob.slice(index, index + length); 81 | } 82 | 83 | function getDataHelper(byteLength, bytes) { 84 | var dataBuffer, dataArray; 85 | dataBuffer = new ArrayBuffer(byteLength); 86 | dataArray = new Uint8Array(dataBuffer); 87 | if (bytes) 88 | dataArray.set(bytes, 0); 89 | return { 90 | buffer : dataBuffer, 91 | array : dataArray, 92 | view : new DataView(dataBuffer) 93 | }; 94 | } 95 | 96 | // Readers 97 | function Reader() { 98 | } 99 | 100 | function TextReader(text) { 101 | var that = this, blobReader; 102 | 103 | function init(callback, onerror) { 104 | var blobBuilder = new BlobBuilder(); 105 | blobBuilder.append(text); 106 | blobReader = new BlobReader(blobBuilder.getBlob("text/plain")); 107 | blobReader.init(function() { 108 | that.size = blobReader.size; 109 | callback(); 110 | }, onerror); 111 | } 112 | 113 | function readUint8Array(index, length, callback, onerror) { 114 | blobReader.readUint8Array(index, length, callback, onerror); 115 | } 116 | 117 | that.size = 0; 118 | that.init = init; 119 | that.readUint8Array = readUint8Array; 120 | } 121 | TextReader.prototype = new Reader(); 122 | TextReader.prototype.constructor = TextReader; 123 | 124 | function Data64URIReader(dataURI) { 125 | var that = this, dataStart; 126 | 127 | function init(callback, onerror) { 128 | var dataEnd = dataURI.length; 129 | while (dataURI.charAt(dataEnd - 1) == "=") 130 | dataEnd--; 131 | dataStart = dataURI.indexOf(",") + 1; 132 | that.size = Math.floor((dataEnd - dataStart) * 0.75); 133 | callback(); 134 | } 135 | 136 | function readUint8Array(index, length, callback, onerror) { 137 | var i, data = getDataHelper(length); 138 | var start = Math.floor(index / 3) * 4; 139 | var end = Math.ceil((index + length) / 3) * 4; 140 | var bytes = obj.atob(dataURI.substring(start + dataStart, end + dataStart)); 141 | var delta = index - Math.floor(start / 4) * 3; 142 | for (i = delta; i < delta + length; i++) 143 | data.array[i - delta] = bytes.charCodeAt(i); 144 | callback(data.array); 145 | } 146 | 147 | that.size = 0; 148 | that.init = init; 149 | that.readUint8Array = readUint8Array; 150 | } 151 | Data64URIReader.prototype = new Reader(); 152 | Data64URIReader.prototype.constructor = Data64URIReader; 153 | 154 | function BlobReader(blob) { 155 | var that = this; 156 | 157 | function init(callback, onerror) { 158 | this.size = blob.size; 159 | callback(); 160 | } 161 | 162 | function readUint8Array(index, length, callback, onerror) { 163 | var reader = new FileReader(); 164 | reader.onload = function(e) { 165 | callback(new Uint8Array(e.target.result)); 166 | }; 167 | reader.onerror = onerror; 168 | reader.readAsArrayBuffer(blobSlice(blob, index, length)); 169 | } 170 | 171 | that.size = 0; 172 | that.init = init; 173 | that.readUint8Array = readUint8Array; 174 | } 175 | BlobReader.prototype = new Reader(); 176 | BlobReader.prototype.constructor = BlobReader; 177 | 178 | function HttpReader(url) { 179 | var that = this; 180 | 181 | function getData(callback, onerror) { 182 | var request; 183 | if (!that.data) { 184 | request = new XMLHttpRequest(); 185 | request.addEventListener("load", function() { 186 | if (!that.size) 187 | that.size = Number(request.getResponseHeader("Content-Length")); 188 | request.response; 189 | that.data = new Uint8Array(request.response); 190 | callback(); 191 | }, false); 192 | request.addEventListener("error", onerror, false); 193 | request.open("GET", url); 194 | request.responseType = "arraybuffer"; 195 | request.send(); 196 | } else 197 | callback(); 198 | } 199 | 200 | function init(callback, onerror) { 201 | var request = new XMLHttpRequest(); 202 | request.onreadystatechange = function (e) { 203 | if (request.readyState === 4 && request.status === 200) { 204 | that.data = new Uint8Array(request.response); 205 | that.size = that.data.byteLength; 206 | callback(); 207 | } 208 | } 209 | /* 210 | request.addEventListener("badload", function() { 211 | that.size = Number(request.getResponseHeader("Content-Length")); 212 | dump("Got data size: " + that.size + "\n"); 213 | //callback(); 214 | }, false); 215 | */ 216 | request.addEventListener("error", onerror, false); 217 | request.open("GET", url, true); 218 | request.responseType = 'arraybuffer'; 219 | request.send(null); 220 | } 221 | 222 | function readUint8Array(index, length, callback, onerror) { 223 | getData(function() { 224 | callback(new Uint8Array(that.data.subarray(index, index + length))); 225 | }, onerror); 226 | } 227 | 228 | that.size = 0; 229 | that.init = init; 230 | that.readUint8Array = readUint8Array; 231 | } 232 | HttpReader.prototype = new Reader(); 233 | HttpReader.prototype.constructor = HttpReader; 234 | 235 | function HttpRangeReader(url) { 236 | var that = this; 237 | 238 | function init(callback, onerror) { 239 | var request = new XMLHttpRequest(); 240 | request.addEventListener("load", function() { 241 | that.size = Number(request.getResponseHeader("Content-Length")); 242 | if (request.getResponseHeader("Accept-Ranges") == "bytes") 243 | callback(); 244 | else 245 | onerror(ERR_HTTP_RANGE); 246 | }, false); 247 | request.addEventListener("error", onerror, false); 248 | request.open("HEAD", url); 249 | request.send(); 250 | } 251 | 252 | function readArrayBuffer(index, length, callback, onerror) { 253 | var request = new XMLHttpRequest(); 254 | request.open("GET", url); 255 | request.responseType = "arraybuffer"; 256 | request.setRequestHeader("Range", "bytes=" + index + "-" + (index + length - 1)); 257 | request.addEventListener("load", function() { 258 | callback(request.response); 259 | }, false); 260 | request.addEventListener("error", onerror, false); 261 | request.send(); 262 | } 263 | 264 | function readUint8Array(index, length, callback, onerror) { 265 | readArrayBuffer(index, length, function(arraybuffer) { 266 | callback(new Uint8Array(arraybuffer)); 267 | }, onerror); 268 | } 269 | 270 | that.size = 0; 271 | that.init = init; 272 | that.readUint8Array = readUint8Array; 273 | } 274 | HttpRangeReader.prototype = new Reader(); 275 | HttpRangeReader.prototype.constructor = HttpRangeReader; 276 | 277 | // Writers 278 | 279 | function Writer() { 280 | } 281 | Writer.prototype.getData = function(callback) { 282 | callback(this.data); 283 | }; 284 | 285 | function TextWriter() { 286 | var that = this, blobBuilder; 287 | 288 | function init(callback, onerror) { 289 | blobBuilder = new BlobBuilder(); 290 | callback(); 291 | } 292 | 293 | function writeUint8Array(array, callback, onerror) { 294 | blobBuilder.append(array.buffer); 295 | callback(); 296 | } 297 | 298 | function getData(callback) { 299 | var reader = new FileReader(); 300 | reader.onload = function(e) { 301 | callback(e.target.result); 302 | }; 303 | reader.onerror = onerror; 304 | reader.readAsText(blobBuilder.getBlob("text/plain")); 305 | } 306 | 307 | that.init = init; 308 | that.writeUint8Array = writeUint8Array; 309 | that.getData = getData; 310 | } 311 | TextWriter.prototype = new Writer(); 312 | TextWriter.prototype.constructor = TextWriter; 313 | 314 | function Data64URIWriter(contentType) { 315 | var that = this, data = "", pending = ""; 316 | 317 | function init(callback, onerror) { 318 | data += "data:" + (contentType || "") + ";base64,"; 319 | callback(); 320 | } 321 | 322 | function writeUint8Array(array, callback, onerror) { 323 | var i, delta = pending.length, dataString = pending; 324 | pending = ""; 325 | for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++) 326 | dataString += String.fromCharCode(array[i]); 327 | for (; i < array.length; i++) 328 | pending += String.fromCharCode(array[i]); 329 | if (dataString.length > 2) 330 | data += obj.btoa(dataString); 331 | else 332 | pending = dataString; 333 | callback(); 334 | } 335 | 336 | function getData(callback) { 337 | callback(data + obj.btoa(pending)); 338 | } 339 | 340 | that.init = init; 341 | that.writeUint8Array = writeUint8Array; 342 | that.getData = getData; 343 | } 344 | Data64URIWriter.prototype = new Writer(); 345 | Data64URIWriter.prototype.constructor = Data64URIWriter; 346 | 347 | function FileWriter(fileEntry, contentType) { 348 | var writer, that = this; 349 | 350 | function init(callback, onerror) { 351 | fileEntry.createWriter(function(fileWriter) { 352 | writer = fileWriter; 353 | callback(); 354 | }, onerror); 355 | } 356 | 357 | function writeUint8Array(array, callback, onerror) { 358 | var blobBuilder = new BlobBuilder(); 359 | blobBuilder.append(array.buffer); 360 | writer.onwrite = function() { 361 | writer.onwrite = null; 362 | callback(); 363 | }; 364 | writer.onerror = onerror; 365 | writer.write(blobBuilder.getBlob(contentType)); 366 | } 367 | 368 | function getData(callback) { 369 | fileEntry.file(callback); 370 | } 371 | 372 | that.init = init; 373 | that.writeUint8Array = writeUint8Array; 374 | that.getData = getData; 375 | } 376 | FileWriter.prototype = new Writer(); 377 | FileWriter.prototype.constructor = FileWriter; 378 | 379 | function BlobWriter(contentType) { 380 | var blobBuilder, that = this; 381 | 382 | function init(callback, onerror) { 383 | blobBuilder = new BlobBuilder(); 384 | callback(); 385 | } 386 | 387 | function writeUint8Array(array, callback, onerror) { 388 | blobBuilder.append(array.buffer); 389 | callback(); 390 | } 391 | 392 | function getData(callback) { 393 | callback(blobBuilder.getBlob(contentType)); 394 | } 395 | 396 | that.init = init; 397 | that.writeUint8Array = writeUint8Array; 398 | that.getData = getData; 399 | } 400 | BlobWriter.prototype = new Writer(); 401 | BlobWriter.prototype.constructor = BlobWriter; 402 | 403 | // inflate/deflate core functions 404 | 405 | function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) { 406 | var chunkIndex = 0, index, outputSize; 407 | 408 | function onflush() { 409 | worker.removeEventListener("message", onmessage, false); 410 | onend(outputSize); 411 | } 412 | 413 | function onmessage(event) { 414 | var message = event.data, data = message.data; 415 | 416 | if (message.onappend) { 417 | outputSize += data.length; 418 | writer.writeUint8Array(data, function() { 419 | onappend(false, data); 420 | step(); 421 | }, onwriteerror); 422 | } 423 | if (message.onflush) 424 | if (data) { 425 | outputSize += data.length; 426 | writer.writeUint8Array(data, function() { 427 | onappend(false, data); 428 | onflush(); 429 | }, onwriteerror); 430 | } else 431 | onflush(); 432 | if (message.progress && onprogress) 433 | onprogress(index + message.current, size); 434 | } 435 | 436 | function step() { 437 | index = chunkIndex * CHUNK_SIZE; 438 | if (index < size) 439 | reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) { 440 | worker.postMessage({ 441 | append : true, 442 | data : array 443 | }); 444 | chunkIndex++; 445 | if (onprogress) 446 | onprogress(index, size); 447 | onappend(true, array); 448 | }, onreaderror); 449 | else 450 | worker.postMessage({ 451 | flush : true 452 | }); 453 | } 454 | 455 | outputSize = 0; 456 | worker.addEventListener("message", onmessage, false); 457 | step(); 458 | } 459 | 460 | function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) { 461 | var chunkIndex = 0, index, outputSize = 0; 462 | 463 | function step() { 464 | var outputData; 465 | index = chunkIndex * CHUNK_SIZE; 466 | if (index < size) 467 | reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(inputData) { 468 | var outputData = process.append(inputData, function() { 469 | if (onprogress) 470 | onprogress(offset + index, size); 471 | }); 472 | outputSize += outputData.length; 473 | onappend(true, inputData); 474 | writer.writeUint8Array(outputData, function() { 475 | onappend(false, outputData); 476 | chunkIndex++; 477 | setTimeout(step, 1); 478 | }, onwriteerror); 479 | if (onprogress) 480 | onprogress(index, size); 481 | }, onreaderror); 482 | else { 483 | outputData = process.flush(); 484 | if (outputData) { 485 | outputSize += outputData.length; 486 | writer.writeUint8Array(outputData, function() { 487 | onappend(false, outputData); 488 | onend(outputSize); 489 | }, onwriteerror); 490 | } else 491 | onend(outputSize); 492 | } 493 | } 494 | 495 | step(); 496 | } 497 | 498 | function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { 499 | var worker, crc32 = new Crc32(); 500 | 501 | function oninflateappend(sending, array) { 502 | if (computeCrc32 && !sending) 503 | crc32.append(array); 504 | } 505 | 506 | function oninflateend(outputSize) { 507 | onend(outputSize, crc32.get()); 508 | } 509 | 510 | if (obj.zip.useWebWorkers) { 511 | worker = new Worker(obj.zip.workerScriptsPath + INFLATE_JS); 512 | launchWorkerProcess(worker, reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror); 513 | } else 514 | launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror); 515 | return worker; 516 | } 517 | 518 | function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) { 519 | var worker, crc32 = new Crc32(); 520 | 521 | function ondeflateappend(sending, array) { 522 | if (sending) 523 | crc32.append(array); 524 | } 525 | 526 | function ondeflateend(outputSize) { 527 | onend(outputSize, crc32.get()); 528 | } 529 | 530 | function onmessage() { 531 | worker.removeEventListener("message", onmessage, false); 532 | launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror); 533 | } 534 | 535 | if (obj.zip.useWebWorkers) { 536 | worker = new Worker(obj.zip.workerScriptsPath + DEFLATE_JS); 537 | worker.addEventListener("message", onmessage, false); 538 | worker.postMessage({ 539 | init : true, 540 | level : level 541 | }); 542 | } else 543 | launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror); 544 | return worker; 545 | } 546 | 547 | function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) { 548 | var chunkIndex = 0, crc32 = new Crc32(); 549 | 550 | function step() { 551 | var index = chunkIndex * CHUNK_SIZE; 552 | if (index < size) 553 | reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function(array) { 554 | if (computeCrc32) 555 | crc32.append(array); 556 | if (onprogress) 557 | onprogress(index, size, array); 558 | writer.writeUint8Array(array, function() { 559 | chunkIndex++; 560 | step(); 561 | }, onwriteerror); 562 | }, onreaderror); 563 | else 564 | onend(size, crc32.get()); 565 | } 566 | 567 | step(); 568 | } 569 | 570 | // ZipReader 571 | 572 | function decodeASCII(str) { 573 | var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', 574 | '\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', 575 | '\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', 576 | '\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6', 577 | '\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3', 578 | '\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE', 579 | '\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE', 580 | '\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7', 581 | '\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ]; 582 | for (i = 0; i < str.length; i++) { 583 | charCode = str.charCodeAt(i) & 0xFF; 584 | if (charCode > 127) 585 | out += extendedASCII[charCode - 128]; 586 | else 587 | out += String.fromCharCode(charCode); 588 | } 589 | return out; 590 | } 591 | 592 | function decodeUTF8(str_data) { 593 | var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0; 594 | 595 | str_data += ''; 596 | 597 | while (i < str_data.length) { 598 | c1 = str_data.charCodeAt(i); 599 | if (c1 < 128) { 600 | tmp_arr[ac++] = String.fromCharCode(c1); 601 | i++; 602 | } else if (c1 > 191 && c1 < 224) { 603 | c2 = str_data.charCodeAt(i + 1); 604 | tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63)); 605 | i += 2; 606 | } else { 607 | c2 = str_data.charCodeAt(i + 1); 608 | c3 = str_data.charCodeAt(i + 2); 609 | tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 610 | i += 3; 611 | } 612 | } 613 | 614 | return tmp_arr.join(''); 615 | } 616 | 617 | function getString(bytes) { 618 | var i, str = ""; 619 | for (i = 0; i < bytes.length; i++) 620 | str += String.fromCharCode(bytes[i]); 621 | return str; 622 | } 623 | 624 | function getDate(timeRaw) { 625 | var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff; 626 | try { 627 | return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, 628 | (time & 0x001F) * 2, 0); 629 | } catch (e) { 630 | } 631 | } 632 | 633 | function readCommonHeader(entry, data, index, centralDirectory) { 634 | entry.version = data.view.getUint16(index, true); 635 | entry.bitFlag = data.view.getUint16(index + 2, true); 636 | entry.compressionMethod = data.view.getUint16(index + 4, true); 637 | entry.lastModDateRaw = data.view.getUint32(index + 6, true); 638 | entry.lastModDate = getDate(entry.lastModDateRaw); 639 | if ((entry.bitFlag & 0x01) === 0x01) { 640 | onerror(ERR_ENCRYPTED); 641 | return; 642 | } 643 | if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) { 644 | entry.crc32 = data.view.getUint32(index + 10, true); 645 | entry.compressedSize = data.view.getUint32(index + 14, true); 646 | entry.uncompressedSize = data.view.getUint32(index + 18, true); 647 | } 648 | if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) { 649 | onerror(ERR_ZIP64); 650 | return; 651 | } 652 | entry.filenameLength = data.view.getUint16(index + 22, true); 653 | entry.extraFieldLength = data.view.getUint16(index + 24, true); 654 | } 655 | 656 | function createZipReader(reader, onerror) { 657 | function Entry() { 658 | } 659 | 660 | Entry.prototype.getData = function(writer, onend, onprogress, checkCrc32) { 661 | var that = this, worker; 662 | 663 | function terminate(callback, param) { 664 | if (worker) 665 | worker.terminate(); 666 | worker = null; 667 | if (callback) 668 | callback(param); 669 | } 670 | 671 | function testCrc32(crc32) { 672 | var dataCrc32 = getDataHelper(4); 673 | dataCrc32.view.setUint32(0, crc32); 674 | return that.crc32 == dataCrc32.view.getUint32(0); 675 | } 676 | 677 | function getWriterData(uncompressedSize, crc32) { 678 | if (checkCrc32 && !testCrc32(crc32)) 679 | onreaderror(); 680 | else 681 | writer.getData(function(data) { 682 | terminate(onend, data); 683 | }); 684 | } 685 | 686 | function onreaderror() { 687 | terminate(onerror, ERR_READ_DATA); 688 | } 689 | 690 | function onwriteerror() { 691 | terminate(onerror, ERR_WRITE_DATA); 692 | } 693 | 694 | reader.readUint8Array(that.offset, 30, function(bytes) { 695 | var data = getDataHelper(bytes.length, bytes), dataOffset; 696 | if (data.view.getUint32(0) != 0x504b0304) { 697 | onerror(ERR_BAD_FORMAT); 698 | return; 699 | } 700 | readCommonHeader(that, data, 4); 701 | dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength; 702 | writer.init(function() { 703 | if (that.compressionMethod === 0) 704 | copy(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror); 705 | else 706 | worker = inflate(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror); 707 | }, onwriteerror); 708 | }, onreaderror); 709 | }; 710 | 711 | return { 712 | getEntries : function(callback) { 713 | if (reader.size < 22) { 714 | onerror(ERR_BAD_FORMAT); 715 | return; 716 | } 717 | reader.readUint8Array(reader.size - 22, 22, function(bytes) { 718 | var dataView = getDataHelper(bytes.length, bytes).view, datalength, fileslength; 719 | if (dataView.getUint32(0) != 0x504b0506) { 720 | onerror(ERR_BAD_FORMAT); 721 | return; 722 | } 723 | datalength = dataView.getUint32(16, true); 724 | fileslength = dataView.getUint16(8, true); 725 | reader.readUint8Array(datalength, reader.size - datalength, function(bytes) { 726 | var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes); 727 | for (i = 0; i < fileslength; i++) { 728 | entry = new Entry(); 729 | if (data.view.getUint32(index) != 0x504b0102) { 730 | onerror(ERR_BAD_FORMAT); 731 | return; 732 | } 733 | readCommonHeader(entry, data, index + 6, true); 734 | entry.commentLength = data.view.getUint16(index + 32, true); 735 | entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10); 736 | entry.offset = data.view.getUint32(index + 42, true); 737 | filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength)); 738 | entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename); 739 | if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/") 740 | entry.directory = true; 741 | comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46 742 | + entry.filenameLength + entry.extraFieldLength + entry.commentLength)); 743 | entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment); 744 | entries.push(entry); 745 | index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength; 746 | } 747 | callback(entries); 748 | }, function() { 749 | onerror(ERR_READ); 750 | }); 751 | }, function() { 752 | onerror(ERR_READ); 753 | }); 754 | }, 755 | close : function(callback) { 756 | if (callback) 757 | callback(); 758 | } 759 | }; 760 | } 761 | 762 | // ZipWriter 763 | 764 | function encodeUTF8(string) { 765 | var n, c1, enc, utftext = [], start = 0, end = 0, stringl = string.length; 766 | for (n = 0; n < stringl; n++) { 767 | c1 = string.charCodeAt(n); 768 | enc = null; 769 | if (c1 < 128) 770 | end++; 771 | else if (c1 > 127 && c1 < 2048) 772 | enc = String.fromCharCode((c1 >> 6) | 192) + String.fromCharCode((c1 & 63) | 128); 773 | else 774 | enc = String.fromCharCode((c1 >> 12) | 224) + String.fromCharCode(((c1 >> 6) & 63) | 128) + String.fromCharCode((c1 & 63) | 128); 775 | if (enc != null) { 776 | if (end > start) 777 | utftext += string.slice(start, end); 778 | utftext += enc; 779 | start = end = n + 1; 780 | } 781 | } 782 | if (end > start) 783 | utftext += string.slice(start, stringl); 784 | return utftext; 785 | } 786 | 787 | function getBytes(str) { 788 | var i, array = []; 789 | for (i = 0; i < str.length; i++) 790 | array.push(str.charCodeAt(i)); 791 | return array; 792 | } 793 | 794 | function createZipWriter(writer, onerror, dontDeflate) { 795 | var worker, files = [], filenames = [], datalength = 0; 796 | 797 | function terminate(callback, message) { 798 | if (worker) 799 | worker.terminate(); 800 | worker = null; 801 | if (callback) 802 | callback(message); 803 | } 804 | 805 | function onwriteerror() { 806 | terminate(onerror, ERR_WRITE); 807 | } 808 | 809 | function onreaderror() { 810 | terminate(onerror, ERR_READ_DATA); 811 | } 812 | 813 | return { 814 | add : function(name, reader, onend, onprogress, options) { 815 | var header, filename, date; 816 | 817 | function writeHeader(callback) { 818 | var data; 819 | date = options.lastModDate || new Date(); 820 | header = getDataHelper(26); 821 | files[name] = { 822 | headerArray : header.array, 823 | directory : options.directory, 824 | filename : filename, 825 | offset : datalength, 826 | comment : getBytes(encodeUTF8(options.comment || "")) 827 | }; 828 | header.view.setUint32(0, 0x14000808); 829 | if (options.version) 830 | header.view.setUint8(0, options.version); 831 | if (!dontDeflate && options.level != 0) 832 | header.view.setUint16(4, 0x0800); 833 | header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true); 834 | header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true); 835 | header.view.setUint16(22, filename.length, true); 836 | data = getDataHelper(30 + filename.length); 837 | data.view.setUint32(0, 0x504b0304); 838 | data.array.set(header.array, 4); 839 | data.array.set([], 30); // FIXME: remove when chrome 18 will be stable (14: OK, 16: KO, 17: OK) 840 | data.array.set(filename, 30); 841 | datalength += data.array.length; 842 | writer.writeUint8Array(data.array, callback, onwriteerror); 843 | } 844 | 845 | function writeFooter(compressedLength, crc32) { 846 | var footer = getDataHelper(16); 847 | datalength += compressedLength || 0; 848 | footer.view.setUint32(0, 0x504b0708); 849 | if (typeof crc32 != "undefined") { 850 | header.view.setUint32(10, crc32, true); 851 | footer.view.setUint32(4, crc32, true); 852 | } 853 | if (reader) { 854 | footer.view.setUint32(8, compressedLength, true); 855 | header.view.setUint32(14, compressedLength, true); 856 | footer.view.setUint32(12, reader.size, true); 857 | header.view.setUint32(18, reader.size, true); 858 | } 859 | writer.writeUint8Array(footer.array, function() { 860 | datalength += 16; 861 | terminate(onend); 862 | }, onwriteerror); 863 | } 864 | 865 | function writeFile() { 866 | options = options || {}; 867 | name = name.trim(); 868 | if (options.directory && name.charAt(name.length - 1) != "/") 869 | name += "/"; 870 | if (files[name]) 871 | throw ERR_DUPLICATED_NAME; 872 | filename = getBytes(encodeUTF8(name)); 873 | filenames.push(name); 874 | writeHeader(function() { 875 | if (reader) 876 | if (dontDeflate || options.level == 0) 877 | copy(reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror); 878 | else 879 | worker = deflate(reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror); 880 | else 881 | writeFooter(); 882 | }, onwriteerror); 883 | } 884 | 885 | if (reader) 886 | reader.init(writeFile, onreaderror); 887 | else 888 | writeFile(); 889 | }, 890 | close : function(callback) { 891 | var data, length = 0, index = 0; 892 | filenames.forEach(function(name) { 893 | var file = files[name]; 894 | length += 46 + file.filename.length + file.comment.length; 895 | }); 896 | data = getDataHelper(length + 22); 897 | filenames.forEach(function(name) { 898 | var file = files[name]; 899 | data.view.setUint32(index, 0x504b0102); 900 | data.view.setUint16(index + 4, 0x1400); 901 | data.array.set(file.headerArray, index + 6); 902 | data.view.setUint16(index + 32, file.comment.length, true); 903 | if (file.directory) 904 | data.view.setUint8(index + 38, 0x10); 905 | data.view.setUint32(index + 42, file.offset, true); 906 | data.array.set(file.filename, index + 46); 907 | data.array.set(file.comment, index + 46 + file.filename.length); 908 | index += 46 + file.filename.length + file.comment.length; 909 | }); 910 | data.view.setUint32(index, 0x504b0506); 911 | data.view.setUint16(index + 8, filenames.length, true); 912 | data.view.setUint16(index + 10, filenames.length, true); 913 | data.view.setUint32(index + 12, length, true); 914 | data.view.setUint32(index + 16, datalength, true); 915 | writer.writeUint8Array(data.array, function() { 916 | terminate(function() { 917 | writer.getData(callback); 918 | }); 919 | }, onwriteerror); 920 | } 921 | }; 922 | } 923 | 924 | if (typeof BlobBuilder == "undefined") { 925 | BlobBuilder = function() { 926 | var that = this, blobParts = [ new Blob() ]; 927 | that.append = function(data) { 928 | blobParts.push(data); 929 | }; 930 | that.getBlob = function(contentType) { 931 | if (blobParts.length > 1 || blobParts[0].type != contentType) { 932 | blobParts = [ contentType ? new Blob(blobParts, { 933 | type : contentType 934 | }) : new Blob(blobParts) ]; 935 | } 936 | return blobParts[0]; 937 | }; 938 | }; 939 | } 940 | 941 | obj.zip = { 942 | Reader : Reader, 943 | Writer : Writer, 944 | BlobReader : BlobReader, 945 | HttpReader : HttpReader, 946 | HttpRangeReader : HttpRangeReader, 947 | Data64URIReader : Data64URIReader, 948 | TextReader : TextReader, 949 | BlobWriter : BlobWriter, 950 | FileWriter : FileWriter, 951 | Data64URIWriter : Data64URIWriter, 952 | TextWriter : TextWriter, 953 | createReader : function(reader, callback, onerror) { 954 | reader.init(function() { 955 | callback(createZipReader(reader, onerror)); 956 | }, onerror); 957 | }, 958 | createWriter : function(writer, callback, onerror, dontDeflate) { 959 | writer.init(function() { 960 | callback(createZipWriter(writer, onerror, dontDeflate)); 961 | }, onerror); 962 | }, 963 | workerScriptsPath : "", 964 | useWebWorkers : true 965 | }; 966 | 967 | })(this); 968 | -------------------------------------------------------------------------------- /run_webserver.sh: -------------------------------------------------------------------------------- 1 | python -m SimpleHTTPServer 2 | print "http://localhost:8000/index.html?usesample" 3 | -------------------------------------------------------------------------------- /sample.log: -------------------------------------------------------------------------------- 1 | { 2 | "threads": [ 3 | { 4 | "name" : "Main Thread", 5 | "samples": [ 6 | { 7 | "name": "(root)", 8 | "frames": [ 9 | { 10 | "location": "Main" 11 | }, 12 | { 13 | "location": "Process Event" 14 | }, 15 | { 16 | "location": "js_mozilla() (in mozilla.com)" 17 | } 18 | ] 19 | }, 20 | { 21 | "name": "(root)", 22 | "frames": [ 23 | { 24 | "location": "Main" 25 | }, 26 | { 27 | "location": "Process Event" 28 | }, 29 | { 30 | "location": "onclick (in XUL)" 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /tests/README: -------------------------------------------------------------------------------- 1 | To run a test case manually use the query parameter '?usesample=tests/FrameView.dat'. 2 | --------------------------------------------------------------------------------